Promise Generator Async 的原理(万字!)

440 阅读29分钟

介绍

本文写的比较多,不感兴趣的小伙伴可以根据标题直接跳到对应部分,阅读本文可以了解到~

  • 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

  1. promise 就是一个类,promise 有三个状态:成功态 resolved 失败态 rejected 等待态 pending(既不成功也不失败)
  2. promise 默认执行器时立即执行
  3. promise (状态变更无法改变)-旦成功就不能失败, 反过来也一样的
  4. 使用resolve 和 reject 函数来改变状态
  5. 如果执行函数时发生了异常也会执行失败逻辑
  6. 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, 回调就无法触发. 所以继续改造~

  1. promise 调用 then 方法时,可能当前的 promise 并没有成功 -- pending 态
  2. --> 发布订阅模式: 如果当前状态是 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的链式调用~

  1. then 方法的返回值是一个新的 promise
  2. 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链式调用,但是一些特殊情况还无法处理~

  1. then 的 成功/失败 回调返回值是一个新的 promise, 会根据新 promise 的状态决定then返回promise的状态(也就是决定下一个then是走成功还是失败的回调)
  2. then 的 成功/失败 回调方法执行错误时, then返回promise的状态变为失败(rejected)
  3. 当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回调的返回结果

题目分析写脑壳痛 🤦‍♀, 应该算比较详细了吧 这一小节全部处理完所有的特殊情况!!

  1. 对then回调返回做统一处理(如: 不能返回promise自己,没有处理thenable等)(符合 promise A+ 标准)
  2. 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 还有一写方法, 不过相对上面的内容会简单不少.

  1. 静态方法 resolve reject all race
  2. 原型方法 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 )相对上面修改了一些错别字

  1. npm i promises-aplus-tests -g
  2. 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;

image.png

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()结束

  1. 有个 _context 对象 存储走到哪一步,下一步是哪一步 (每个foo()都会生成一个新的generator 所以每次也会跟着生成一个 context)
  2. 有个regeneratorRuntime.mark 方法 传入foo
  3. 有个regeneratorRuntime.wrap 方法传入 foo$ mark返回值 null [6,13]
  4. foo() 的返回是个对象 有 next throw return 等方法
  5. next() 调用会执行 下一步的 case
  6. throw() 会在之前执行到的地方抛出错误
  7. 通过 _context.abrupt 保存返回值 (return后面没有代码了 所以是在case end输出最后结果)
  8. 通过 _context.step 改变done状态 并输出最后值

generator 原理介绍和简单实现

根据上面总结,我们先实现一部分的代码

  1. 每个生成器都会有一个_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() {}
}
  1. 有个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;
};
  1. 有个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 打印输出了 大概总结下:

  1. 每个generator调用的时候, 返回 一个含有next方法的对象,创建一个context维护prev/next等属性记录当前在哪一步(执行到哪个switch的case)
  2. 每当调用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 已经可以完成了. 但是还有几个显而易见的问题

  1. next 可以一直执行 返回的 done 始终为false说明未结束
  2. 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等方法

还存在的问题~ 这一块就不讲这么仔细了

  1. next方法有一个参数,yield表达式本身没有返回值,或者说总是返回undefined,该参数就会被当作上一个yield表达式的返回值.
  2. 结束之后调用的next方法,value值要为undefined,上面为最后一次的值
  3. 添加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的特点

  1. 可以在内部抛出错误
  2. 如果内部没有拦截到,可以在函数体外抛出错误
  3. 执行完会自动执行下一次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的特点~

  1. async方法返回是一个 promise
  2. 返回的 promise 只有所有代码执行完才会改变状态为成功(不报错情况下)
  3. 返回的 promise 执行的await过程中 await返回了一个失败状态的promise, 如果错误没有被捕获,就会中断整个函数的执行
  4. await 后面返回的普通值 会被转化为 Promise.resolve(值) (微任务执行)
  5. 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);
    });
  });
}

大吉大利终于写完了, 参考了一些大佬的文章, 侵删.