介绍
本文写的比较多,不感兴趣的小伙伴可以根据标题直接跳到对应部分,阅读本文可以了解到~
Promise
符合A+标准实现Generator
内部原理和简单实现async/await
是怎么样的 Generator 语法糖
异步写法的发展历史
1. CallBack 回调
- 缺点: 依赖的请求会形成多层嵌套(回调地狱),难以维护。
// 需求:根据A的内容读取B的内容,根据B的内容读取C的内容...
fs.readFile(A, "utf-8", function (err, data) {
//...
fs.readFile(B, "utf-8", function (err, data) {
//...
fs.readFile(C, "utf-8", function (err, data) {
//....
fs.readFile(D, "utf-8", function (err, data) {
//....
});
});
});
});
2. Promise 链式调用
- 优点: 链式调用一定程度上解决了回调地狱的问题, 统一的错误处理
- 缺点: promise 无法中止, 只能通过返回pending状态的Promise来进行中断
// 需求:根据A的内容读取B的内容,根据B的内容读取C的内容
// 先把readFile回调封装成promise
const promisify = (fn) => (...args) => {
return new Promise((resolve, reject) => {
fn(...args, function (err, data) {
if (err) reject(err);
resolve(data);
});
});
}
let read = promisify(fs.readFile);
// 嵌套的代码就转化成了链式写法 A的返回值传通过then传给B B的返回值通过then传给C
read(A)
.then((data) => {
//...
return read(B);
})
.then((data) => {
//...
return read(C);
})
.then((data) => {
//...
return read(D);
})
.catch((err) => {
console.error("读取失败", err);
});
3. Generator + Co
- 优点: 配合co模块实现了异步请求的同步写法
- 缺点: 有一定学习成本, 必须配合额外的模块(co等)
// 需求:根据A的内容读取B的内容,根据B的内容读取C的内容
// 一个简单的co (generator 自执行函数)
const co = (it) =>
new Promise((resolve, reject) => {
function next(data) {
const { value, done } = it.next(data);
if (done) {
resolve(value);
} else {
Promise.resolve(value).then(next, reject);
}
}
next();
});
// 转化为 generator 写法 当然要配合co库才行
function* exec() {
const A = yield read(A);
//...
const B = yield read(B);
//...
const C = yield read(C);
//...
const D = yield read(D);
//...
}
co(exec())
4. Async/await
- 优点: 同步的方式书写异步代码 相对generator优点: 1.内置执行器。2.更好的语义。3.更广的适用性:yield命令后面只能是 Thunk 函数或 Promise 对象。4.返回值是 Promise。
- 缺点: 错误处理比较复杂
// 转化为 async写法 不需要配合其他库 写法简单 逻辑清晰
async function exec() {
try {
const A = await read(A);
//...
const B = await read(B);
//...
const C = await read(C);
//...
const D = await read(D);
//...
} catch (error) {
console.error("读取失败", error);
}
}
exec()
Promise 实现
前面比较简单篇幅较多, 头铁的可以直接看最后的完整版本, 看注释也能看个大概
1. 实现一个同步版本的基础 promise
- promise 就是一个类,promise 有三个状态:成功态 resolved 失败态 rejected 等待态 pending(既不成功也不失败)
- promise 默认执行器时立即执行
- promise (状态变更无法改变)-旦成功就不能失败, 反过来也一样的
- 使用resolve 和 reject 函数来改变状态
- 如果执行函数时发生了异常也会执行失败逻辑
- promise 的实例都拥有一个 then 方法,第一个参数是成功的回调,另一个失败的回调
- 成功状态调, 用成功回调
- 失败状态, 调用失败回调
class Mypromise {
constructor(exector) {
this.states = {
PENDING: "PENDING",
REJECTED: "REJECTED",
RESOVED: "RESOLVED",
};
this.status = this.states.PENDING;
// 成功的结果
this.value = undefined;
// 失败的结果
this.reason = undefined;
// 调用 resolve 1. (pending时)改变状态为成功态(fulfilled) 2. 改变成功值
let resolve = (value) => {
if (this.status === this.states.PENDING) {
this.value = value;
this.status = this.states.RESOVED;
}
};
// 调用 reject 1. (pending时)改变状态为失败态(rejected) 2. 改变失败值
let reject = (reason) => {
if (this.status === this.states.PENDING) {
this.reason = reason;
this.status = this.states.REJECTED;
}
};
try {
// 立即执行 如果报错直接走错误逻辑
exector(resolve, reject);
} catch (e) {
reject(e);
}
}
// 原型上有then方法
// 调用时 1.成功/失败 执行对应的回调函数
then(onFulfilled, onRejected) {
if (this.status === this.states.RESOVED) {
onFulfilled(this.value);
} else if (this.status === this.states.REJECTED) {
onRejected(this.reason);
}
}
}
// 对外暴露
module.exports = Mypromise;
执行测试下我们的代码(同步版)
let promise = new MyPromise((resolve, reject) => {
resolve("success");
});
console.log(1);
promise.then(
(data) => {
console.log(`data: ${data}`);
},
(err) => {
console.log(`err: ${err}`);
}
);
console.log(2)
// 分析:
// 1. 实例化 MyPromise(初始化状态结果等), 执行传入callback
// 2. 执行 resolve 状态变更(pending -> fulfilled) 传入值(success)赋值给this.value
// 3. 执行 console.log(1) 打印: 1
// 4. 执行 then 方法, 当前状态为 fulfilled 走成功回调并传入value 打印: data: success
// 5. 执行 console.log(2) 打印: 2
结果:
1
data: success
2
2. promise 异步调用处理
上面我们实现的同步版本的promise, 但是如果resolve是异步调用的, 就会导致执行 then 方法的时候状态还是 pending, 回调就无法触发. 所以继续改造~
- promise 调用 then 方法时,可能当前的 promise 并没有成功 -- pending 态
- --> 发布订阅模式: 如果当前状态是 pending, 我们需要将成功的回调和失败的回调存放起来,稍后调用 resolve 和 reject 的时候重新执行
constructor(exector) {
this.states = {
PENDING: "PENDING",
REJECTED: "REJECTED",
RESOVED: "RESOLVED",
};
this.status = this.states.PENDING;
this.value = undefined;
this.reason = undefined;
// 增加 成功/失败 回调队列 (调用then时 pending态下 把回调添加到对应队列)
// 同一个promise 可以能会有多个then 所以要写成队列
+ this.onResolvedCallbacks = [];
+ this.onRejectedCallbacks = [];
let resolve = (value) => {
if (this.status === this.states.PENDING) {
this.value = value;
this.status = this.states.RESOVED;
// then 添加到队列之后, 异步执行resolve
// 1. 改变状态和结果 2. 执行成功队列中的回调
+ this.onResolvedCallbacks.forEach((cb) => cb());
}
};
let reject = (reason) => {
if (this.status === this.states.PENDING) {
this.reason = reason;
this.status = this.states.REJECTED;
// then 添加到队列之后, 异步执行resolve
// 1. 改变状态和结果 2. 执行失败队列中的回调
+ this.onRejectedCallbacks.forEach((cb) => cb());
}
};
try {
exector(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
if (this.status === this.states.RESOVED) {
onFulfilled(this.value);
} else if (this.status === this.states.REJECTED) {
onRejected(this.reason);
// 如果resolve是异步执行, 那么执行到 then 方法时状态未变更
// pending 状态下, 把 成功/失败 回调添加到对应队列
+ } else if (this.status === this.states.PENDING) {
+ this.onResolvedCallbacks.push(() => {
+ onFulfilled(this.value);
+ });
+ this.onRejectedCallbacks.push(() => {
+ console.log(this.status);
+ onRejected(this.reason);
+ });
}
}
测试下异步情况~
let promise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("success");
});
});
promise.then(
(data) => {
console.log(`data1: ${data}`);
},
(err) => {
console.log(`err1: ${err}`);
}
);
promise.then(
(data) => {
console.log(`data2: ${data}`);
},
(err) => {
console.log(`err3: ${err}`);
}
);
// 分析:
// 1. 实例化 MyPromise(初始化状态结果等), 执行executor(立即执行执行器)
// 2. 执行 setTimeout ---> 宏任务1
// 3. 执行第一个 then 方法, 当前状态 pending, 把传入的成功/失败回调添加到对应的成功/失败队列
// 4. 执行第二个 then 方法, 当前状态 pending, 把传入的成功/失败回调添加到对应的成功/失败队列(此时 成功/失败 队列中 都有两个回调)
// 5. 同步任务执行完, 没有微任务队列, 宏任务队列中有等待执行任务(宏任务1:第二步添加的),
// 开始执行宏任务回调.
// 6. 执行 resolve, 状态变更(pending -> fulfilled) this.value值修改,执行成功回调队列.
// 7. 触发队列按照先进先出打印 data1: success data2: success
结果:
data1: success
data2: success
3. then 方法的链式调用(这一步比较复杂拆开来说)
上面实现的 promsie 还是会有回调地狱的问题 (上一个 promise 的结果只能在自己的 then 中获取) 所以接下来我们来实现then的链式调用~
- then 方法的返回值是一个新的 promise
- then 方法返回值(是普通值,先实现不是promise的情况), 传递给外层下一个then的成功回调(如果是普通值,不管是上一个then的成功还是失败都会走下一个then的成功回调! 先不处理错误情况)
then(onFulfilled, onRejected) {
// 返回值是一个新的 Promise, 所以把方法都包裹在新的promise 执行器中
+ let promise2 = new Mypromise((resolve, reject) => {
// 当 同步调用resolve/reject 时, 状态已改变走 成功或失败 项
if (this.status === this.states.RESOVED) {
// fulfilled时, 直接执行then的成功回调并传入值
+ let x = onFulfilled(this.value);
// then 的成功回调返回值x, 通过新 promise2 的 resolve 传递给新函数的then
// then函数又返回这个 promise2 从而实现链式调用
+ resolve(x);
} else if (this.status === this.states.REJECTED) {
// rejected时, 直接执行then的成功回调并传入值
+ let x = onRejected(this.reason);
// 这里逻辑同上
+ resolve(x);
} else if (this.status === this.states.PENDING) {
// 依次执行then方法的成功/失败回调(可能有多个)
this.onResolvedCallbacks.push(() => {
// 同成功判断的逻辑
+ let x = onFulfilled(this.value);
+ resolve(x);
});
this.onRejectedCallbacks.push(() => {
// 同失败判断的逻辑
+ let x = onRejected(this.reason);
+ resolve(x);
});
}
+ });
+ return promise2;
}
测试下链式调用~
let promise = new MyPromise((resolve, reject) => { // 1
setTimeout(() => { // 2
reject("failed"); // 3
});
});
promise
.then( // 4
(data) => { // 5
console.log(`data1: ${data}`);
return 123;
},
(err) => { // 6
console.log(`err1: ${err}`);
return err;
}
)
.then( // 7
(data) => { // 8
console.log(`data2: ${data}`);
},
(err) => { // 9
console.log(`err2: ${err}`);
}
);
/*
分析: 代码后面添加了序号方便说明 第一个Promise称为P1后续都加1 方便理解
1. 执行1: 实例化 MyPromise(初始化状态结果等), 执行executor(立即执行执行器)
2. 执行2: 执行 setTimeout ---> 宏任务1
3. 执行4: 执行第一个 then 方法
3.1 实例化一个新的 P2, 执行执行器
3.2 P2执行器执行: P1 的状态为 pending, 所以走 pending 项, 把成功和失败回调添加
到对应的(P1中)队列
3.3 返回 P2, 跳出
4. 执行7: 执行第二个 then 方法(此时执行的是 P2 的 then 方法)
4.1 实例化一个新的 P3, 执行执行器
4.2 P3执行器执行: P2 的状态为 pending, 所以走 pending 项, 把成功和失败回调添加
到对应的(P2中)队列
4.3 返回 P3, 跳出
5. 同步任务执行完毕, 没有微任务, 宏任务队列有等待任务(宏任务1),执行宏任务回调
6. 执行3: reject('failed'), 状态变更(pending -> rejected) this.reason值修改,
执行失败回调队列.
7. 执行6: 触发 P1 的失败回调队列 (输入: err1: failed)
7.1 返回(err就是P1的reason)'field'
7.2 返回值传递给 P2 的 resolve 并执行, 改变 P2 的状态为 fulfilled,
改变value值为failed,执行 P2 成功回调队列(执行8) (输出 data2: field)
*/
结果:
err1: failed
data2: failed
3.1 处理 then 方法回调返回promise的情况和回调中的错误
上面实现了基础的promsie链式调用,但是一些特殊情况还无法处理~
- then 的 成功/失败 回调返回值是一个新的 promise, 会根据新 promise 的状态决定then返回promise的状态(也就是决定下一个then是走成功还是失败的回调)
- then 的 成功/失败 回调方法执行错误时, then返回promise的状态变为失败(rejected)
- 当promise的resolve/reject不是异步的,then里的成功或失败回调也应该是异步的(上面还是同步的) 实现完这一步 基本这个 promise就差不多了 后面就是完善各种特殊情况~
then(onFulfilled, onRejected) {
let promise2 = new Mypromise((resolve, reject) => {
// 把成功和失败回调封装成方法
+ const resolveCallback = () => {
+ queueMicrotask(() => {
+ // 捕捉 onFulfilled 的错误, 如果报错直接改变新promise状态为失败reject
+ // 加了queueMicrotask, promise2的executor外层的try catch就捕获不到错误了,所以这+ // 里要加报错处理
+ try {
+ let x = onFulfilled(this.value);
+ // 判断成功回调返回值是否为Promise,如果是就等待新promise状态变更为成功或失败,
+ // 把成功/失败的回调触发新 promise2 的 resolve/reject修改promise2的状态
+ // 如果是普通值 直接调用promise2的resolve
+ // 下面同理
+ x instanceof Mypromise ? x.then(resolve, reject) : resolve(x);
+ } catch (error) {
+ reject(error);
+ }
+ });
+ };
+ const rejectedCallback = () => {
+ queueMicrotask(() => {
+ try {
+ let x = onRejected(this.reason);
+ x instanceof Mypromise ? x.then(resolve, reject) : resolve(x);
+ } catch (error) {
+ reject(error);
+ }
+ });
+ };
if (this.status === this.states.RESOVED) {
+ resolveCallback();
} else if (this.status === this.states.REJECTED) {
+ rejectedCallback();
} else if (this.status === this.states.PENDING) {
this.onResolvedCallbacks.push(() => {
+ resolveCallback();
});
this.onRejectedCallbacks.push(() => {
+ rejectedCallback();
});
}
});
return promise2;
}
测试then的成功或失败返回值为promise的情况~~
let promise = new MyPromise((resolve, reject) => { // 1
// 如果没有用 queueMicrotask 变成微任务打印结果 同步1 将会在两个then之间
// reject("failed");
setTimeout(() => { // 2
reject("failed"); // 3
});
});
let p2 = promise.then( // 4
(data) => { // 5
console.log(`data1: ${data}`);
},
(err) => { // 6
console.log(`err1: ${err}`);
return new MyPromise((resolve, reject) => reject(123));
}
);
console.log("同步1"); // 7
p2.then( // 8
(data) => { // 9
console.log(`data2: ${data}`);
},
(err) => { // 10
console.log(`err3: ${err}`);
}
);
/*
分析: 代码后面添加了序号方便说明 第一个Promise称为P1后续都加1 方便理解
1. 执行1: 实例化 MyPromise(初始化状态结果等), 执行executor(立即执行执行器)
2. 执行2: 执行 setTimeout ---> 宏任务1
3. 执行4: 执行第一个 then 方法
3.1 实例化一个新的 P2, 执行执行器
3.2 P2执行器执行: P1 的状态为 pending, 所以走 pending 项, 把成功和失败回调添加
到对应的(P1中)队列
3.3 返回 P2, 跳出
4. 执行8: 打印 同步1
5. 执行9: 执行第二个 then 方法(此时执行的是 P2 的 then 方法)
5.1 实例化一个新的 P3, 执行执行器
5.2 P3执行器执行: P2 的状态为 pending, 所以走 pending 项, 把成功和失败回调添加
到对应的(P2中)队列
5.3 返回 P3, 跳出
6. 同步任务执行完毕, 没有微任务, 宏任务队列有等待任务(宏任务1),执行宏任务回调
7. 执行3: reject('failed'), 状态变更(pending -> rejected) this.reason值修改为
failed,执行失败回调队列. ---> 微任务1
8. 当前同步任务开始执行 微任务1 <---
8. 执行6: 触发 P1 的失败回调队列 (输入: err1: failed)
7.1 执行7: 实例化一个新的 P4,执行 P4 的 execut ,执行reject改变为失败状态
7.2 返回 P4 (rejected态)
7.3 判断 P4 是否是 Promise
- 是: 执行 P4 的then方法, P4是rejected状态, 所以直接触发then失败的
回调(这个回调方法是直接用的P2的reject), 改变 P2 的状态为 rejected,
改变reason值为 123,执行 P2 失败回调队列(执行11) ---> 微任务2 (输出 err3: 123)
- 否: P4 传递给 P2 的 resolve 并执行, 改变 P2 的状态为 fulfilled,
改变value值为 P4 ,执行 P2 成功回调队列(执行10) ---> 微任务2 (输出 data2: P3)
*/
结果:
同步1
err1: failed
err3: 123
3.2 定义resolvePromise函数统一处理then回调的返回结果
题目分析写脑壳痛 🤦♀, 应该算比较详细了吧 这一小节全部处理完所有的特殊情况!!
- 对then回调返回做统一处理(如: 不能返回promise自己,没有处理thenable等)(符合 promise A+ 标准)
- then的值穿透 p1.then().then().then(d=>console.log(d))
then(onFulfilled, onRejected) {
+ // 值穿透 如果成功回调不是function 就会无视并重置为 v=>v 把值传给下一层的成功回调
+ onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) => v;
+ // 值穿透 如果失败回调不是function 就会无视并重置为 v=>throw v 把值传给下一层的失败回调
+ onRejected =
+ typeof onRejected === "function"
+ ? onRejected
+ : (err) => {
+ throw err;
+ };
let promise2 = new Mypromise((resolve, reject) => {
// 把成功和失败回调封装成方法
const resolveCallback = () => {
// queueMicrotask 把任务变成微任务 不再是同步任务
queueMicrotask(() => {
// 捕捉 onFulfilled 的错误, 如果报错直接改变新promise状态为失败reject
try {
let x = onFulfilled(this.value);
// 判断成功回调返回值是否为Promise,如果是就等待新promise状态变更为成功或失败,
// 把成功/失败的回调触发新 promise2 的 resolve/reject修改promise2的状态
// 如果是普通值 直接调用promise2的resolve
// 下面同理
// 对then 回调的返回值做统一处理
+ resolvePromise(promise2, x, resolve, reject);
// 判断不符合 A+ 规范, 没有考虑 thenable的情况
// x instanceof Mypromise ? x.then(resolve, reject) : resolve(x);
} catch (error) {
reject(error);
}
});
};
const rejectedCallback = () => {
queueMicrotask(() => {
try {
let x = onRejected(this.reason);
+ resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error);
}
});
};
if (this.status === this.states.RESOVED) {
resolveCallback();
} else if (this.status === this.states.REJECTED) {
rejectedCallback();
} else if (this.status === this.states.PENDING) {
this.onResolvedCallbacks.push(() => {
resolveCallback();
});
this.onRejectedCallbacks.push(() => {
rejectedCallback();
});
}
});
return promise2;
}
// 大头!
+const resolvePromise = (promise2, x, resolve, reject) => {
+ // 当返回值是自身时 触发报错 防止无限循环
+ if (promise2 === x) {
+ // 用一个类型错误结束掉 promise
+ return reject(
+ new TypeError("Chaining cycle detected for promise #<Promise>")
+ );
+ }
+ // 防止失败了再次进入成功/成功了再次进入失败,普通值不需处理(不加这个过不了A+规范的测试 ==.)
+ // 但是想不到什么场景下会触发多次(知道的伙伴可以回复我~)
+ let called;
+ // 根据A+标准 判断是否是 thenable 或是 function;
+ if ((typeof x === "object" && x != null) || typeof x === "function") {
+ try {
+ let then = x.then;
+ if (typeof then === "function") {
+ // 只能认为是一个promise了
+ then.call(
+ x,
+ (y) => {
+ if (called) return;
+ called = true;
+ // 根据 promise的状态决定是成功还是失败
+ resolvePromise(promise2, y, resolve, reject); // 递归解析的过程
+ },
+ (e) => {
+ if (called) return;
+ called = true;
+ reject(e);
+ }
+ );
+ } else {
+ // 普通值 可能是 {then: '12'}
+ resolve(x);
+ }
+ } catch (e) {
+ if (called) return;
+ called = true;
+ reject(e);
+ }
+ } else {
+ // 普通值
+ resolve(x);
+ }
+};
最终测试~~
let p = new MyPromise((resolve, reject) => { // 1
resolve(1); // 2
});
let p2 = p
.then( // 3
(data) => { // 4
console.log('date1',data)
return new MyPromise((resolve, reject) => { // 5
resolve( // 6
new MyPromise((resolve, reject) => { // 7
resolve(2); // 8
})
);
});
})
.then() // 9 值穿透相当于 .then(d=>d,e=>{throw e})
.then( // 10
(data) => { // 11
console.log("data2", data);
},
(err) => { // 12
console.log("err", err);
}
);
/*
分析: 代码后面添加了序号方便说明 第一个Promise称为P1后续都加1 方便理解 ---> 表示进队列
1. 执行1: 实例化 MyPromise(初始化状态结果等) P1, 执行executor(立即执行执行器)
2. 执行2: 执行resolve 改变 P1 状态(pending -> fulfilled) value(null -> 1)
执行成功队列,队列为空.
3. 执行3: 执行第一个 then 方法
3.1 实例化一个新的 P2, 执行执行器
3.2 P2执行器执行: P1 的状态为 fulfilled, 所以走成功项 ---> 微任务1(P1)
3.3 返回 P2, 跳出
4. 执行9: 执行第二个 then 方法
4.1 没有成功/失败的回调参数,给两个参数复制为默认值 成功回调v=>v 失败 e=>{throw e}
4.2 实例化一个新的 P3, 执行执行器
4.3 P3 执行器执行: P2 的状态为 pending, 所以走 pending 项, 把默认的成功和失败回
调添加到对应的(P2中)队列
4.4 返回 P3, 跳出
5. 执行10: 执行第三个 then 方法(此时执行的是 P3 的 then 方法)
5.1 实例化一个新的 P4, 执行执行器
5.2 P4 执行器执行: P3 的状态为 pending, 所以走 pending 项, 把成功和失败回调添加
到对应的(P3中)队列
5.3 返回 P4, 跳出
6. 同步任务执行完毕, 微任务队列中有[微任务1(P1)],执行 微任务1 <---
7. 执行4: 微任务1回调,执行P2成功回调 [[ 打印 date1 1 ]]
7.1 实例化一个新的 P5, 执行执行器
7.2 执行7: 实例化一个新的 P6, 执行执行器
7.3 执行8: 改变 P6 状态 (pending -> fulfilled) value (null -> 2), 跳出
7.4 执行6: 改变 P5 状态 (pending -> fulfilled) value (null -> P6), 跳出
7.5 P2 then 的成功回调返回值为 P5(成功状态)
7.6 成功回调返回值为P5 类型是promise,执行 P5 的 then 方法, P5是fulfilled状态,
所以直接触发P5的成功回调. ---> 微任务(P5) , 微任务(P5) <--- 微任务队列空
7.7 P5 成功回调的传参是 P6, 还是Promise, 执行 P6 的 then 方法, P6是fulfilled
状态, 触发P6的成功回调, P6的成功回调通过P2的resolve把值(2)传递给P2的then成功回调,
跳出. ---> 微任务(P6) , 微任务(P6) <--- 微任务队列空
8. 执行 P2 的成功回调(执行9), P2 成功回调没有参数,默认 v => v, v是p6传递下来的值为3, 是
普通值, 触发 P3 的resolve(3), 跳出 ---> 微任务(P2) , 微任务(P2) <--- 微任务队列空
9. 执行11: 触发P3成功回调, [[ 打印: data2 2 ]], 回调返回值是undefined, undefined
是普通值, 改变 P4 的状态为 fulfilled,跳出---> 微任务(P3) , 微任务(P3) <--- 微任务队列空
*/
结果:
date1 1
data2 2
4. promsie一些自带方法实现
promise 还有一写方法, 不过相对上面的内容会简单不少.
- 静态方法 resolve reject all race
- 原型方法 catch finally
//catch方法其实就是执行一下then的第二个回调
catch(rejectFn) {
return this.then(undefined, rejectFn);
}
// finally方法 成功/失败都会走callback 返回不影响后面then的结果
// 如果是成功的 promise,会用之前的成功结果
// 但如果是失败的 promise,会用他的失败原因传给下一个
finally(callback) {
return this.then(
//执行回调,并return value传递给后面的then
(value) => Mypromise.resolve(callback()).then(() => value),
(reason) =>
Mypromise.resolve(callback()).then(() => {
throw reason;
}) //reject同理
);
}
//静态的resolve方法, 如果参数是Promise实例, 直接返回这个实例
static resolve(value) {
if (value instanceof MyPromise) return value;
return new MyPromise((resolve) => resolve(value));
}
//静态的reject方法
static reject(reason) {
return new MyPromise((resolve, reject) => reject(reason));
}
//静态的all方法
static all(promiseArr) {
let index = 0;
let result = [];
return new MyPromise((resolve, reject) => {
promiseArr.forEach((p, i) => {
// 不为promise的值转也转化为promise
MyPromise.resolve(p).then(
(val) => {
index++;
result[i] = val;
// 每有一个返回index就会+1,比较和传入队列的长度,一致就返回结果
if (index === promiseArr.length) {
resolve(result);
}
},
(err) => {
// 错误就返回错误的结果
reject(err);
}
);
});
});
}
//静态的race方法
static race(promiseArr) {
return new Mypromise((resolve, reject) => {
//同时执行Promise,如果有一个Promise的状态发生改变,就变更新MyPromise的状态
for (let p of promiseArr) {
Mypromise.resolve(p).then(
//Promise.resolve(p)用于处理传入值不为Promise的情况
resolve, //注意这个resolve是上边new MyPromise的
reject
);
}
});
}
5. (完整代码)通过promises-aplus-tests测试
下面就是完整版了,通过了测试( 872 passing )相对上面修改了一些错别字
- npm i promises-aplus-tests -g
- promises-aplus-tests ./promise.js 运行对应文件
// 对then 回调返回值做统一处理
const resolvePromise = (promise2, x, resolve, reject) => {
// 当返回值是自身时 触发报错 防止无限循环
if (promise2 === x) {
// 用一个类型错误结束掉 promise
return reject(
new TypeError("Chaining cycle detected for promise #<Promise>")
);
}
// 防止失败了再次进入成功/成功了再次进入失败,普通值不需处理(不加这个过不了A+规范的测试 ==.)
// 但是想不到什么场景下会触发多次(知道的伙伴可以call me)
let called;
// 后续的条件要严格判断保证代能和别的库一起使用;
if ((typeof x === "object" && x != null) || typeof x === "function") {
try {
let then = x.then;
if (typeof then === "function") {
// 只能认为是一个promise了
// 不要写成x.then
then.call(
x,
(y) => {
if (called) return;
called = true;
// 根据 promise的状态决定是成功还是失败
resolvePromise(promise2, y, resolve, reject); // 递归解析的过程
},
(e) => {
if (called) return;
called = true;
reject(e);
}
);
} else {
// {then: '12'}
// 普通值
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
// 普通值
resolve(x);
}
};
class MyPromise {
constructor(executor) {
this.states = {
PENDING: "PENDING",
REJECTED: "REJECTED",
RESOLVED: "RESOLVED",
};
this.status = this.states.PENDING;
this.value = undefined; // 成功值
this.reason = undefined; // 失败值
this.onResolvedCallbacks = []; // 成功回调
this.onRejectedCallbacks = []; // 失败回调
let resolve = (value) => {
// 加不加都能通过测试, 不加会和原生的表现不一致
if (value instanceof MyPromise) {
return value.then(resolve, reject);
}
if (this.status === this.states.PENDING) {
// then 添加到队列之后, 异步执行resolve
// 1. 改变状态和结果 2. 执行成功队列中的回调
this.value = value;
this.status = this.states.RESOLVED;
this.onResolvedCallbacks.forEach((cb) => cb());
}
};
let reject = (reason) => {
if (this.status === this.states.PENDING) {
this.reason = reason;
this.status = this.states.REJECTED;
this.onRejectedCallbacks.forEach((cb) => cb());
}
};
try {
// 立即执行传入回调
executor(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
// 值穿透 如果成功回调不是function 就会无视并重置为 v=>v 把值传给下一层的成功回调
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) => v;
// 值穿透 如果失败回调不是function 就会无视并重置为 v=>throw v 把值传给下一层的失败回调
onRejected =
typeof onRejected === "function"
? onRejected
: (err) => {
throw err;
};
let promise2 = new MyPromise((resolve, reject) => {
// 把成功和失败回调封装成方法
const resolveCallback = () => {
// queueMicrotask 把任务变成微任务 不再是同步任务
queueMicrotask(() => {
// 捕捉 onFulfilled 的错误, 如果报错直接改变新promise状态为失败reject
try {
let x = onFulfilled(this.value);
// 判断成功回调返回值是否为Promise,如果是就等待新promise状态变更为成功或失败,
// 把成功/失败的回调触发新 promise2 的 resolve/reject修改promise2的状态
// 如果是普通值 直接调用promise2的resolve
// 下面同理
resolvePromise(promise2, x, resolve, reject);
// 判断不符合 A+ 规范, 没有考虑 thenable的情况
// x instanceof MyPromise ? x.then(resolve, reject) : resolve(x);
} catch (error) {
reject(error);
}
});
};
const rejectedCallback = () => {
queueMicrotask(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
// x instanceof MyPromise ? x.then(resolve, reject) : resolve(x);
} catch (error) {
reject(error);
}
});
};
if (this.status === this.states.RESOLVED) {
resolveCallback();
} else if (this.status === this.states.REJECTED) {
rejectedCallback();
} else if (this.status === this.states.PENDING) {
this.onResolvedCallbacks.push(() => {
resolveCallback();
});
this.onRejectedCallbacks.push(() => {
rejectedCallback();
});
}
});
return promise2;
}
//catch方法其实就是执行一下then的第二个回调
catch(rejectFn) {
return this.then(undefined, rejectFn);
}
// finally方法 成功/失败都会走callback 返回不影响后面then的结果
// 如果是成功的 promise,会用之前的成功结果
// 但如果是失败的 promise,会用他的失败原因传给下一个
finally(callback) {
return this.then(
//执行回调,并return value传递给后面的then
(value) => Mypromise.resolve(callback()).then(() => value),
(reason) =>
Mypromise.resolve(callback()).then(() => {
throw reason;
}) //reject同理
);
}
//静态的resolve方法, 如果参数是Promise实例, 直接返回这个实例
static resolve(value) {
if (value instanceof MyPromise) return value;
return new MyPromise((resolve) => resolve(value));
}
//静态的reject方法
static reject(reason) {
return new MyPromise((resolve, reject) => reject(reason));
}
//静态的all方法
static all(promiseArr) {
let index = 0;
let result = [];
return new MyPromise((resolve, reject) => {
promiseArr.forEach((p, i) => {
// 不为promise的值转也转化为promise
MyPromise.resolve(p).then(
(val) => {
index++;
result[i] = val;
// 每有一个返回index就会+1,比较和传入队列的长度,一致就返回结果
if (index === promiseArr.length) {
resolve(result);
}
},
(err) => {
// 错误就返回错误的结果
reject(err);
}
);
});
});
}
//静态的race方法
static race(promiseArr) {
return new MyPromise((resolve, reject) => {
//同时执行Promise,如果有一个Promise的状态发生改变,就变更新MyPromise的状态
// 不为promise的值转也转化为promise
promiseArr.forEach((p, i) => {
//同时执行promise, 第一个返回结果的,直接改变外层promise状态
MyPromise.resolve(p).then(resolve, reject);
});
});
}
}
// 测试插件需要添加这一步
MyPromise.deferred = function () {
var result = {};
result.promise = new MyPromise(function (resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
};
module.exports = MyPromise;
promsie 终于告一段落~
generator 原理和基础实现
简单介绍下使用
// generator 的返回是一个遍历器对象, 可以分步依次调用内部的代码
function* foo() {
yield "result1";
let res2 = yield "result2";
try {
console.log('res2',res2)
yield "result3";
} catch (e) {
console.log("e", e);
}
yield "result4";
return "result5";
}
// 每次next 会执行到下一个yield 后面的语句
// next 的传参会成为 yield 语句的返回值
const gen = foo();
gen.next(); // 输出 {value: 'result1', done: false}
gen.next(); // 输出 {value: 'result2', done: false}
gen.next(123); // 输出 {value: 'result3', done: false} res2 123
gen.next(); // 输出 {value: 'result4', done: false}
gen.next(); // 输出 {value: 'result5', done: true}
分析babel编译后的generator
// 这是一个generator方法
function* foo() {
console.log(1);
yield "result1";
let res2 = yield "result2";
// throw 134
try {
console.log(2, res2);
yield "result3";
console.log(2.1);
} catch (e) {
console.log("e", e);
}
yield "result4";
return "result5";
}
const gen = foo();
gen.next(); // 输出 {value: 'result1', done: false}
gen.next(); // 输出 {value: 'result2', done: false}
gen.next(); // 输出 {value: 'result3', done: false}
gen.throw(); // 输出 {value: 'result4', done: false}
gen.next(); // 输出 {value: 'result5', done: }
// 经过babel编译转化之后
var _marked = /*#__PURE__*/regeneratorRuntime.mark(foo);
function foo() {
var res2;
return regeneratorRuntime.wrap(function foo$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
console.log(1);
_context.next = 3;
return "result1";
case 3:
_context.next = 5;
return "result2";
case 5:
res2 = _context.sent;
_context.prev = 6;
console.log(2, res2);
_context.next = 10;
return "result3";
case 10:
console.log(2.1);
_context.next = 16;
break;
case 13:
_context.prev = 13;
_context.t0 = _context["catch"](6);
console.log("e", _context.t0);
case 16:
_context.next = 18;
return "result4";
case 18:
return _context.abrupt("return", "result5");
case 19:
case "end":
return _context.stop();
}
}
}, _marked, null, [[6, 13]]);
}
var gen = foo();
gen.next();
gen.next();
gen.next();
gen["throw"]();
gen.next();
根据generator的使用特性和编译后的代码分析 我们先得出几个点 观察编译后的代码,每次调用 foo$ 都会走某个case 并且改变 prev和next,通过_context.stop()结束
- 有个 _context 对象 存储走到哪一步,下一步是哪一步 (每个foo()都会生成一个新的generator 所以每次也会跟着生成一个 context)
- 有个regeneratorRuntime.mark 方法 传入foo
- 有个regeneratorRuntime.wrap 方法传入 foo$ mark返回值 null [6,13]
- foo() 的返回是个对象 有 next throw return 等方法
- next() 调用会执行 下一步的 case
- throw() 会在之前执行到的地方抛出错误
- 通过 _context.abrupt 保存返回值 (return后面没有代码了 所以是在case end输出最后结果)
- 通过 _context.step 改变done状态 并输出最后值
generator 原理介绍和简单实现
根据上面总结,我们先实现一部分的代码
- 每个生成器都会有一个_context对象维护内部状态, 所以我们写个构造函数生成 _context对象
// foo$ 根据 prev 和 next 来判断当前在哪一步和下一步再哪一步
const Context = function () {
this.prev = 0; // 当前走到哪一步(哪个switch 的case)
this.next = 0; // 下一步要走哪个 case
this.done = false; // 是否完成
};
Context.prototype = {
constructor: Context,
// 最后异步有一个结束方法
stop() {
// case end 已经在最后一步了 改变完成状态 并返回最终值
this.done = true;
},
// 用来保存 return 的值
abrupt() {}
}
- 有个regeneratorRuntime.mark 方法 传入foo(先不看这个方法内部实现,就当没有返回值)
const regeneratorRuntime = {};
// 返回值还是 foo 本身, regeneratorRuntime.mark 主要是 改变 foo 的 prototype 和 __proto__, 继承一些方法如 next throw 等
regeneratorRuntime.mark = function (genFun) {
// TODO ... 改变 传入方法的 prototype 和 __proto__
// genFun.prototype = Object.create(Gp);
return genFun;
};
- 有个regeneratorRuntime.wrap 方法传入 foo$ mark返回值 null [6,13]
/*
分析下:
1. generator的返回值是一个遍历器对象(不懂就当是一个对象,包含next方法), 所以wrap方法
也应该返回一个遍历器对象.
2. 初始化的时候内部要维护一个context来维护内部状态
*/
// 声明一个对象 当做所有generator 的原型 给这个原型写入 next throw return等方法
const Gp = {}; // generator prototype
/**
*
* @param {*} innerFn 外层的foo
* @param {*} outerFn 内层的foo$ 就是swich case的方法
* @param {*} self 内层foo$的this 这里我们不用 null
* @param {*} tryLocsList 错误对应的 prev 和 next 所在case 二维数组 [[tryLoc,catchLoc]]
* @returns {object} 他的返回值是个对象 有 next throw return 等方法
*/
regeneratorRuntime.wrap = function (innerFn, outerFn, self, tryLocsList) {
// 创建一个返回对象,继承 next throw 等方法
const generator = Object.create(Gp);
// 每个generator 都有自己的context 储存 next prev done 等信息
const _context = new Context();
// next throw有相似之处,有多个方法我们就写在一个方法
// next 要执行innerFn 所以需要 innerFn参数
// innerFn 需要 context 判断执行到哪一步
generator._invoke = makeInvokeMethod(innerFn, self, _context);
// Generate
return generator;
};
// 创建invoke方法, 返回的是一个方法 这个方法就是 next throw return 方法
// 我们先实现下next逻辑 next的时候发生了什么?
// 1. 执行了 foo$ 方法 并且返回 {value,done}
function makeInvokeMethod(innerFn, self, context) {
// 要区分是 next throw 中的哪一个 需要一个 类型参数 method, 方法有传参 需要一个 arg
return function invoke(method, arg) {
if (method === "next") {
// next 逻辑 1. 执行当前case
const value = innerFn.call(self, context);
return { value, done: context.done };
} else if (method === "throw") {
// throw 逻辑
}
// 公共逻辑
};
}
// 给generator Gp定义原型方法
// 对外把方法拆开 next throw return 实际调用还是 invoke
function defineIteratorMethods(prototype) {
["next", "throw", "return"].forEach(function (method) {
prototype[method] = function (arg) {
return this._invoke(method, arg);
};
});
}
defineIteratorMethods(Gp);
经过以上几部 已经可以成功调用next 打印输出了 大概总结下:
- 每个generator调用的时候, 返回 一个含有next方法的对象,创建一个context维护prev/next等属性记录当前在哪一步(执行到哪个switch的case)
- 每当调用next的时候 执行switch case方法, 默认case 0开始执行,执行完改变next指向下一个case, 下次执行foo$ 方法就会直接执行下一个case了, 返回case 的值坐为value
我们测试一下~
var _marked = /*#__PURE__*/ regeneratorRuntime.mark(foo);
function foo() {
var res2;
return regeneratorRuntime.wrap(
function foo$(_context) {
while (1) {
switch ((_context.prev = _context.next)) {
case 0:
console.log(1);
_context.next = 3;
return "result1";
case 3:
_context.next = 5;
return "result2";
case 5:
res2 = _context.sent;
_context.prev = 6;
console.log(2, res2);
_context.next = 10;
return "result3";
case 10:
console.log(2.1);
_context.next = 16;
break;
case 13:
_context.prev = 13;
_context.t0 = _context["catch"](6);
console.log("e", _context.t0);
case 16:
_context.next = 18;
return "result4";
case 18:
return _context.abrupt("return", "result5");
case 19:
case "end":
return _context.stop();
}
}
},
_marked,
null,
[[6, 13]]
);
}
var gen = foo();
console.log("next1:", gen.next());
console.log("next1:", gen.next());
console.log("next1:", gen.next());
console.log("next1:", gen.next());
console.log("next1:", gen.next());
console.log("next1:", gen.next());
/*
返回打印
1
next1: { value: 'result1', done: false }
next1: { value: 'result2', done: false }
2 undefined
next1: { value: 'result3', done: false }
2.1
next1: { value: 'result4', done: false }
next1: { value: undefined, done: false }
next1: { value: undefined, done: false }
*/
这一步我们了解了 generator 的基本运行原理, 并写了一个极简的版本
完善next方法,和done的状态变更
next 已经可以完成了. 但是还有几个显而易见的问题
- next 可以一直执行 返回的 done 始终为false说明未结束
- generator 走到return的时候 done会直接改为结束状态(true) 并返回return的值{value:returnVal,done: true}
// 可以看到 case 18 执行完, 应为没有改变 context.next指向 所以后面的 case end 项没有执行
// 我们要让他执行 所以要在 case 18的 context.abrupt 让他改变next的值为end
// 修改abrupt方法
// 用来判断是invoke的while是否走continue
+ const ContinueSentinel = {};
const Context = function () {
this.prev = 0; // 当前走到哪一步(哪个switch 的case)
this.next = 0; // 下一步要走哪个 case
this.done = false; // 是否完成
+ this.rval = undefined; // 储存return返回值
};
Context.prototype = {
constructor: Context,
// 最后的case有一个结束方法 修改结束状态 并返回return值
stop() {
// 已经在最后一步了 改变完成状态
+ this.done = true;
// 返回return的值
return this.rval;
},
// 保存结果 type是类型 arg是return的结果
abrupt(type, arg) {
+ if (type === "return") {
+ // return 的时候 done就要改为true
+ // 为了让 end被执行
+ this.next = "end";
+ this.rval = arg; // 用context.rval 来存return的结果
+ // invoke的while判断是否走continue,自动走一遍invoke方法(就是next方法) ,也就是case 18 之后 再自动走一遍 casa end 修改状态为true
+ return ContinueSentinel;
+ }
},
};
// 让invoke 走到return的时候能自动执行下一个 switch case 需要改造一下
// 返回的是一个方法 这个方法就是 next throw return 方法
function makeInvokeMethod(innerFn, self, context) {
// 要区分是 next throw 中的哪一个 需要一个 类型参数 method, 方法有传参 需要一个 arg
return function invoke(method, arg) {
// TODO 增加一个循环 让他自动走最有一个case end
+ while (true) {
if (method === "next") {
// next 逻辑 1. 执行当前case
const value = innerFn.call(self, context);
// abrupt 的返回值 如果是ContinueSentinel 就重新执行一遍innerFn
if (value === ContinueSentinel) {
// 状态完成时 走 case end 要再执行一遍方法
continue;
}
return { value, done: context.done };
} else if (method === "throw") {
// throw 逻辑
} else if (method === "return") {
}
// 公共逻辑
+ }
};
}
我们继续测试一下~
var _marked = /*#__PURE__*/ regeneratorRuntime.mark(foo);
function foo() {
var res2;
return regeneratorRuntime.wrap(
function foo$(_context) {
while (1) {
switch ((_context.prev = _context.next)) {
case 0:
console.log(1);
_context.next = 3;
return "result1";
case 3:
_context.next = 5;
return "result2";
case 5:
res2 = _context.sent;
_context.prev = 6;
console.log(2, res2);
_context.next = 10;
return "result3";
case 10:
console.log(2.1);
_context.next = 16;
break;
case 13:
_context.prev = 13;
_context.t0 = _context["catch"](6);
console.log("e", _context.t0);
case 16:
_context.next = 18;
return "result4";
case 18:
return _context.abrupt("return", "result5");
case 19:
case "end":
return _context.stop();
}
}
},
_marked,
null,
[[6, 13]]
);
}
var gen = foo();
console.log("next1:", gen.next());
console.log("next2:", gen.next());
console.log("next3:", gen.next());
console.log("next4:", gen.next());
console.log("next5 return:", gen.next());
console.log("next6:", gen.next());
/*
返回打印
1
next1: { value: 'result1', done: false }
next2: { value: 'result2', done: false }
2 undefined
next3: { value: 'result3', done: false }
2.1
next4: { value: 'result4', done: false }
next5 return: { value: 'result5', done: true } 相对之前 这一步有了value done状态也为true了
next6: { value: 'result5', done: true }
*/
继续完善添加throw return等方法
还存在的问题~ 这一块就不讲这么仔细了
- next方法有一个参数,yield表达式本身没有返回值,或者说总是返回undefined,该参数就会被当作上一个yield表达式的返回值.
- 结束之后调用的next方法,value值要为undefined,上面为最后一次的值
- 添加throw 和 return 方法
//问题1.next 的时候 给context添加一个 sent属性记录上一次的返回值, 下一次的next switch case中就能取到上一次的context.sent
// 在 invoke 的next项中给 存储next传参
context.sent = arg;
// 问题2.结束之后调用的next方法,value值要为undefined,上面为最后一次的值
// 我们在 invoke中添加拦截 当done状态改为true 表示已经执行到最后了
if (context.done) {
return { value: undefined, done: true };
}
接下来看一下 throw的特点
- 可以在内部抛出错误
- 如果内部没有拦截到,可以在函数体外抛出错误
- 执行完会自动执行下一次next 并输出 (看例子 case 10 没有return 会直接自动执行下一次next)
//特点1: 可以在内部抛出错误
// case 13: context上有 catch 方法 传入 tryLoc也就是prev(6) 返回值就是报错内容
// 我们在 context 添加一个tryEntries 数组 应为generator中会有多次try catch 初始化一个值 [{tryLoc: 'root'}] 如果tryLoc 是root 说明错误没有被捕获
// wrap 的第四个参数就是 [[tryLog,catchLog]]
const Context = function (tryLocsList) {
this.prev = 0; // 当前走到哪一步(哪个switch 的case)
this.next = 0; // 下一步要走哪个 case
this.done = false; // 是否完成
this.rval = undefined; // 储存return返回值
+ // tryLoc try开始项 catchLoc catch所在case completion:{type: 类型, arg: 错误值}
+ this.tryEntries = [
+ { tryLoc: "root", completion: { type: "", arg: undefined } },
+ ]; // 储存报错信息
+ tryLocsList.forEach((locs) => {
+ // [6,13]
+ var entry = { tryLoc: locs[0] }; // try 的位置
+ if (1 in locs) {
+ // 如果有catch
+ entry.catchLoc = locs[1]; //catch的位置
+ }
+ entry.completion = { type: "", arg: undefined };
+ this.tryEntries.push(entry);
+ });
+ //[{ tryLoc: 'root' }, { tryLoc: 6, catchLoc: 13 }]
};
// 我们改造下 invoke
// 1. 提取公共部分 都要执行 innerFn(foo$) 方法
// 返回的是一个方法 这个方法就是 next throw return 方法
function makeInvokeMethod(innerFn, self, context) {
// 要区分是 next throw 中的哪一个 需要一个 类型参数 method, 方法有传参 需要一个 arg
return function invoke(method, arg) {
if (method === "throw" && context.done) {
throw arg;
}
if (context.done) {
return { value: undefined, done: true };
}
// 增加一个循环 让他自动走最有一个case end
while (true) {
if (method === "next") {
+ // next 逻辑 1. 执行当前case
+ // 2. 用sent 记录next参数 下一次的next switch case中就能取到上一次的sent
+ context.sent = arg;
+ } else if (method === "throw") {
+ // throw 逻辑 保存错误值 修改next位置
+ context.dispatchException(arg);
+ } else if (method === "return") {
+ context.abrupt("return", arg);
+ }
+ // 公共逻辑
+ const value = innerFn.call(self, context);
+ if (value === ContinueSentinel) {
+ // 状态完成时 走 case end 要再执行一遍方法
+ continue;
+ }
+ return { value, done: context.done };
}
};
}
// 2. throw时 处理错误 保存错误值 修改next位置为 catch所在位置
// 给Context.prototype原型 增加 catch 和 dispatchException 方法
// case 13 根据tryLoc找到对应 tryCatch 返回错误值
catch(tryLoc) {
for (var i = this.tryEntries.length - 1; i >= 0; --i) {
const entry = this.tryEntries[i];
if (tryLoc === entry.tryLoc) {
return entry.completion.arg;
}
}
},
// 处理异常 参数: 错误值 throw方法触发
dispatchException(exception) {
// 储存错误报错
// 改变下一次next的位置
// 从最后面的try catch 位置开始匹配
const context = this;
// 改变tryEntries中对应trycatch completion把错误值存进去
// 改变下一次next的指向到对应的catchLoc catch的case所在位置
function handle(loc, record) {
record.type = "throw";
record.arg = exception;
context.next = loc;
// return !!caught;
}
for (var i = this.tryEntries.length - 1; i >= 0; --i) {
const entry = this.tryEntries[i];
const record = entry.completion;
// 说明错误没有trycatch 到, 在第一个try catch之前报错了
// 没有捕获到错误 改变状态 done: true,value:undefined
if (entry.tryLoc === "root") {
return handle("end", record);
}
// 确认是哪个try catch捕获到
if (entry.tryLoc <= context.prev) {
if (context.prev < entry.catchLoc) {
return handle(entry.catchLoc, record);
}
}
}
},
直接上目前的完整代码
/*
function* foo() {
console.log(1);
yield "result1";
let res2 = yield "result2";
// throw 134
try {
console.log(2, res2);
yield "result3";
console.log(2.1);
} catch (e) {
console.log("e", e);
}
yield "result4";
return "result5";
}
const gen = foo();
gen.next();
gen.next();
gen.throw();
*/
// 我们一步步实现一个简单的generator
// 根据上面代码推断几个规律
// 1. 有个 _context 对象 存储走到哪一步,下一步是哪一步 (每个foo()都会生成一个新的generator 所以每次也会跟着生成一个 context)
// 2. foo() 的返回是个对象 有 next throw 等方法
// 3. next() 调用会执行 下一步的 case
// 4. throw() 会在之前执行到的地方抛出错误
// 每个生成器都会有一个_context对象, 所以我们写个构造函数生成 _context对象
// 参数二维数组 try catch 的位置[[6,13]]
const Context = function (tryLocsList) {
this.prev = 0; // 当前走到哪一步(哪个switch 的case)
this.next = 0; // 下一步要走哪个 case
this.done = false; // 是否完成
this.rval = undefined; // 储存return返回值
// tryLoc try开始项 catchLoc catch所在case completion:{type: 类型, arg: 错误值}
this.tryEntries = [
{ tryLoc: "root", completion: { type: "", arg: undefined } },
]; // 储存报错信息
tryLocsList.forEach((locs) => {
// [6,13]
var entry = { tryLoc: locs[0] }; // try 的位置
if (1 in locs) {
// 如果有catch
entry.catchLoc = locs[1]; //catch的位置
}
entry.completion = { type: "", arg: undefined };
this.tryEntries.push(entry);
});
//[{ tryLoc: 'root' }, { tryLoc: 6, catchLoc: 13 }]
};
const regeneratorRuntime = {};
const Gp = {}; // generator prototype
const ContinueSentinel = {}; // 用来判断是invoke的while是否走continue
Context.prototype = {
constructor: Context,
// 最后异步有一个结束方法
stop() {
// 已经在最后一步了 改变完成状态
this.done = true;
// 返回return的值
return this.rval;
},
// 用来返回 return 的值 type是类型 arg是return的结果
abrupt(type, arg) {
if (type === "return") {
// return 的时候 done就要改为true
// 为了让 end被执行
this.next = "end";
this.rval = arg;
// invoke的while判断是否走continue
return ContinueSentinel;
}
},
catch(tryLoc) {
for (var i = this.tryEntries.length - 1; i >= 0; --i) {
const entry = this.tryEntries[i];
if (tryLoc === entry.tryLoc) {
return entry.completion.arg;
}
}
},
// 处理异常 参数: 错误值 throw方法触发
dispatchException(exception) {
// 储存错误报错
// 改变下一次next的位置
// 从最后面的try catch 位置开始匹配
const context = this;
function handle(loc, record) {
record.type = "throw";
record.arg = exception;
context.next = loc;
// return !!caught;
}
for (var i = this.tryEntries.length - 1; i >= 0; --i) {
const entry = this.tryEntries[i];
const record = entry.completion;
// 说明错误没有trycatch 到, 在第一个try catch之前报错了
// 没有捕获到错误 改变状态 done: true,value:undefined
if (entry.tryLoc === "root") {
return handle("end", record);
}
// 确认是哪个try catch捕获到
if (entry.tryLoc <= context.prev) {
if (context.prev < entry.catchLoc) {
return handle(entry.catchLoc, record);
}
}
}
},
};
// 返回值还是 foo 本身, regeneratorRuntime.mark 主要是 改变 foo 的 prototype 和 __proto__, 继承一些方法 next
regeneratorRuntime.mark = function (genFun) {
// TODO ... 改变 传入方法的 prototype 和 __proto__
// genFun.prototype = Object.create(Gp);
return genFun;
};
/**
*
* @param {*} innerFn 外层的foo
* @param {*} outerFn 内层的foo$ 就是swich case的方法
* @param {*} self 内层foo$的this 这里我们不用 null
* @param {*} tryLocsList 错误对应的 prev 和 next 所在case 二维数组 [[tryLoc,catchLoc]]
* @returns {object} 他的返回值是个对象 有 next throw return 等方法
*/
regeneratorRuntime.wrap = function (innerFn, outerFn, self, tryLocsList) {
// 继承 next throw 等方法
const generator = Object.create(Gp);
// 每个generator 都有自己的context 储存 next prev done 等信息
const _context = new Context(tryLocsList || []);
// next throw有相似之处,有多个方法我们就写在一个方法
// next 要执行innerFn 所以需要 innerFn参数
// innerFn 需要 context 判断执行到哪一步
generator._invoke = makeInvokeMethod(innerFn, self, _context);
// Generate
return generator;
};
// 对外把方法拆开
function defineIteratorMethods(prototype) {
["next", "throw", "return"].forEach(function (method) {
prototype[method] = function (arg) {
return this._invoke(method, arg);
};
});
}
defineIteratorMethods(Gp);
// 返回的是一个方法 这个方法就是 next throw return 方法
function makeInvokeMethod(innerFn, self, context) {
// 要区分是 next throw 中的哪一个 需要一个 类型参数 method, 方法有传参 需要一个 arg
return function invoke(method, arg) {
// 增加一个循环 让他自动走最有一个case end
if (method === "throw" && context.done) {
throw arg;
}
if (context.done) {
return { value: undefined, done: true };
}
while (true) {
if (method === "next") {
// next 逻辑 1. 执行当前case
// 2. 用sent 记录next参数 下一次的next switch case中就能取到上一次的sent
context.sent = arg;
} else if (method === "throw") {
// throw 逻辑 保存错误值 修改next位置
context.dispatchException(arg);
} else if (method === "return") {
context.abrupt("return", arg);
}
// 公共逻辑
const value = innerFn.call(self, context);
if (value === ContinueSentinel) {
// 状态完成时 走 case end 要再执行一遍方法
continue;
}
return { value, done: context.done };
}
};
}
// 经过babel转化之后 ~
// 返回值还是 foo 本身, regeneratorRuntime.mark 主要是 改变 foo 的 prototype 和 __proto__ 可以先无视
var _marked = /*#__PURE__*/ regeneratorRuntime.mark(foo);
function foo() {
var res2;
return regeneratorRuntime.wrap(
function foo$(_context) {
while (1) {
switch ((_context.prev = _context.next)) {
case 0:
console.log(1);
_context.next = 3;
return "result1";
case 3:
_context.next = 5;
return "result2";
case 5:
res2 = _context.sent;
_context.prev = 6;
console.log(2, res2);
_context.next = 10;
return "result3";
case 10:
console.log(2.1);
_context.next = 16;
break;
case 13:
_context.prev = 13;
_context.t0 = _context["catch"](6);
console.log("e", _context.t0);
case 16:
_context.next = 18;
return "result4";
case 18:
return _context.abrupt("return", "result5");
case 19:
case "end":
return _context.stop();
}
}
},
_marked,
null,
[[6, 13]]
);
}
var gen = foo();
console.log("next1:", gen.next());
console.log("next2:", gen.next());
console.log("next3:", gen.next());
console.log("return:", gen.return(234234));
// console.log("throw4:", gen.throw(123));
console.log("next5 return:", gen.next());
console.log("next6:", gen.next());
//打印
/*
1
next1: { value: 'result1', done: false }
next2: { value: 'result2', done: false }
2 undefined
next3: { value: 'result3', done: false }
return: { value: 234234, done: true }
next5 return: { value: undefined, done: true }
next6: { value: undefined, done: true }
*/
偷懒了... 符合需求 over~ 源码大致也是上述的实现 简单版 有兴趣的可以去看一遍源码,有了上面的印象之后,看的就比较快了
async await 语法糖如何实现
秃了秃了 破万字了~ 本章先了解下 generator 在异步中是怎么运用的 然后介绍 async 是怎么样的 generator 语法糖 babel编译的的 async 和generator 区别不大 底层还是generator
generator 在异步中是怎么运用的
上🐴
//模拟请求
const db = {
post(a, b = true) { // 默认是成功 a是返回值 b控制成功失败
return new Promise((resolve, reject) => {
setTimeout(() => {
if (b) resolve(a);
reject(a);
}, 1000);
});
},
};
// db.post这个请求是传入什么返回什么
// 发起3次请求 每次请求返回的值加1 传递给下一次请求
function* dbFuc() {
const res = [];
res[0] = yield db.post(1);
res[1] = yield db.post(res[0] + 1);
res[2] = yield db.post(res[1] + 1);
return res;
}
// 如果不配合co模块 使用generator来调用异步 就会出现以下情况
// gen.next() 获取结果 {value: promise<pending>,done:false}
// 第二个请求依赖第一个请求的结果 就会形成嵌套 下面的代码看的头皮发麻(回调地狱)
// 第一个请求的value通过第二个next的参数,替换第一个yield的返回值传递给后续代码
const gen = dbFuc();
gen.next().value.then((res) => {
console.log("res1", res); //{ a: 1 }
gen.next(res).value.then((res1) => {
console.log("res2", res1); //{ a: 2 }
gen.next(res1).value.then((res2) => {
console.log("res3", res); //{ a: 3 }
console.log("return", gen.next(res2)); //{ value: [ 1, 2, 3 ], done: true }
});
});
});
// 我们写一个简单的 co 模块 (就是自动执行generator)
let co = (it) =>
new Promise((resolve, reject) => {
// 异步送代靠的是回调函数;
function next(data) {
const { value, done } = it.next(data);
if (done) {
resolve(value);
} else {
Promise.resolve(value).then(next, reject);
}
}
next();
});
co(dbFuc()).then((d) => console.log("d", d));
//打印 d [ 1, 2, 3 ]
加个co模块之后 generator在异步中的使用就好了很多不需要在自己调用传值,但是还是有些麻烦,这时候就有了async/await. 可以大概的说 async/await == generator + co
async/await 语法糖实现
大概表述下async的特点~
- async方法返回是一个 promise
- 返回的 promise 只有所有代码执行完才会改变状态为成功(不报错情况下)
- 返回的 promise 执行的await过程中 await返回了一个失败状态的promise, 如果错误没有被捕获,就会中断整个函数的执行
- await 后面返回的普通值 会被转化为 Promise.resolve(值) (微任务执行)
- await 会返回后面的值
直接上代码 偷个懒~ 一行行解释
async function dbFuc() {
const res = [];
res[0] = await db.post(1);
res[1] = await db.post(res[0] + 1);
res[2] = await db.post(res[1] + 1);
return res;
}
//转化为 generator 写法
function dbFuc1(db) {
return spawn(function* () {
const res = [];
res[0] = yield db.post(1);
res[1] = yield db.post(res[0] + 1);
res[2] = yield db.post(res[1] + 1);
return res;
});
}
function spawn(genF) {
// 返回是一个Promise
return new Promise(function (resolve, reject) {
const gen = genF(); // 先获取遍历器对象 方便后面自动执行
function step(nextF) {
let next;
try {
next = nextF(); // 执行 gen.next() 用方法包裹是方便在这里统一try catch
} catch (e) {
return reject(e); // 如果代码next执行过程中出现报错直接改变返回promise的状态为rejected
}
if (next.done) { // 如果为true,表示代码执行到最后了,直接把return的值传给返回promise,并改变状态未 fulfilled
return resolve(next.value);
}
//通过 Promise.resolve转化值保证后续是(微任务执行)
Promise.resolve(next.value).then(
function (v) { // 获取成功值 传递给gen.next的参数,返回给await(yeild)前面的值;
// 重复调用执行下一步
step(function () {
return gen.next(v);
});
},
function (e) { // 失败就抛出错误 会在上面try catch中执行,如果generator内部有trycatch 就会被内部捕获, 如果没有捕获就会被上的trycatch捕获直接改变返回promise状态 结束执行
step(function () {
// generator 的throw方法可以在函数体外抛出错误,然后在 Generator 函数体内捕获
// throw方法被捕获以后,报错错后会执行到下一个yield
// 如果内部没有捕获到错误 会直接中断generator函数
return gen.throw(e);
});
}
);
}
// 方法调用的时候直接执行 第一次next
step(function () {
return gen.next(undefined);
});
});
}
大吉大利终于写完了, 参考了一些大佬的文章, 侵删.