1. Promise的实现原理
  2. async/await的实现原理
  3. Generator的实现原理

    Promise实现

    在成文过程中,笔者查阅了很多讲解Promise实现的文章,但感觉大多文章都很难称得上条理清晰,有的上来就放大段Promise规范翻译,有的在Promise基础使用上浪费篇幅,又或者把一个简单的东西长篇大论,过度讲解,我推荐头铁的同学直接拉到本章小结看最终实现,结合着注释直接啃代码也能理解十之八九
    回归正题,文章开头我们先点一下Promise为我们解决了什么问题:在传统的异步编程中,如果异步之间存在依赖关系,我们就需要通过层层嵌套回调来满足这种依赖,如果嵌套层数过多,可读性和可维护性都变得很差,产生所谓“回调地狱”,而Promise将回调嵌套改为链式调用,增加可读性和可维护性。下面我们就来一步步实现一个Promise:

    1. 观察者模式

    我们先来看一个最简单的Promise使用:
    1. const p1 = new Promise((resolve, reject) => {
    2. setTimeout(() => {
    3. resolve('result')
    4. },
    5. 1000);
    6. })
    7. p1.then(res => console.log(res), err => console.log(err))
    观察这个例子,我们分析Promise的调用流程:
  • Promise的构造方法接收一个executor(),在new Promise()时就立刻执行这个executor回调
  • executor()内部的异步任务被放入宏/微任务队列,等待执行
  • then()被执行,收集成功/失败回调,放入成功/失败队列
  • executor()的异步任务被执行,触发resolve/reject,从成功/失败队列中取出回调依次执行

其实熟悉设计模式的同学,很容易就能意识到这是个观察者模式,这种收集依赖 -> 触发通知 -> 取出依赖执行 的方式,被广泛运用于观察者模式的实现,在Promise里,执行顺序是then收集依赖 -> 异步触发resolve -> resolve执行依赖。依此,我们可以勾勒出Promise的大致形状:

  1. class MyPromise {
  2. // 构造方法接收一个回调
  3. constructor(executor) {
  4. this._resolveQueue = [] // then收集的执行成功的回调队列
  5. this._rejectQueue = [] // then收集的执行失败的回调队列
  6. // 由于resolve/reject是在executor内部被调用, 因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue
  7. let _resolve = (val) => {
  8. // 从成功队列里取出回调依次执行
  9. while(this._resolveQueue.length) {
  10. const callback = this._resolveQueue.shift()
  11. callback(val)
  12. }
  13. }
  14. // 实现同resolve
  15. let _reject = (val) => {
  16. while(this._rejectQueue.length) {
  17. const callback = this._rejectQueue.shift()
  18. callback(val)
  19. }
  20. }
  21. // new Promise()时立即执行executor,并传入resolve和reject
  22. executor(_resolve, _reject)
  23. }
  24. // then方法,接收一个成功的回调和一个失败的回调,并push进对应队列
  25. then(resolveFn, rejectFn) {
  26. this._resolveQueue.push(resolveFn)
  27. this._rejectQueue.push(rejectFn)
  28. }
  29. }

写完代码我们可以测试一下:

  1. const p1 = new MyPromise((resolve, reject) => {
  2. setTimeout(() => {
  3. resolve('result')
  4. }, 1000);
  5. })
  6. p1.then(res => console.log(res))
  7. //一秒后输出result

我们运用观察者模式简单的实现了一下thenresolve,使我们能够在then方法的回调里取得异步操作的返回值,但我们这个Promise离最终实现还有很长的距离,下面我们来一步步补充这个Promise:

2. Promise A+规范

上面我们已经简单地实现了一个超低配版Promise,但我们会看到很多文章和我们写的不一样,他们的Promise实现中还引入了各种状态控制,这是由于ES6的Promise实现需要遵循Promise/A+规范,是规范对Promise的状态控制做了要求。Promise/A+的规范比较长,这里只总结两条核心规则:

  1. Promise本质是一个状态机,且状态只能为以下三种:Pending(等待态)Fulfilled(执行态)Rejected(拒绝态),状态的变更是单向的,只能从Pending -> Fulfilled 或 Pending -> Rejected,状态变更不可逆
  2. then方法接收两个可选参数,分别对应状态改变时触发的回调。then方法返回一个promise。then 方法可以被同一个 promise 调用多次。

你真的了解Promise、async/await、Generator的实现原理吗? - 图1根据规范,我们补充一下Promise的代码:

  1. //Promise/A+规范的三种状态
  2. const PENDING = 'pending'
  3. const FULFILLED = 'fulfilled'
  4. const REJECTED = 'rejected'
  5. class MyPromise {
  6. // 构造方法接收一个回调
  7. constructor(executor) {
  8. this._status = PENDING // Promise状态
  9. this._resolveQueue = [] // 成功队列, resolve时触发
  10. this._rejectQueue = [] // 失败队列, reject时触发
  11. // 由于resolve/reject是在executor内部被调用, 因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue
  12. let _resolve = (val) => {
  13. if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected"
  14. this._status = FULFILLED // 变更状态
  15. // 这里之所以使用一个队列来储存回调,是为了实现规范要求的 "then 方法可以被同一个 promise 调用多次"
  16. // 如果使用一个变量而非队列来储存回调,那么即使多次p1.then()也只会执行一次回调
  17. while(this._resolveQueue.length) {
  18. const callback = this._resolveQueue.shift()
  19. callback(val)
  20. }
  21. }
  22. // 实现同resolve
  23. let _reject = (val) => {
  24. if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected"
  25. this._status = REJECTED // 变更状态
  26. while(this._rejectQueue.length) {
  27. const callback = this._rejectQueue.shift()
  28. callback(val)
  29. }
  30. }
  31. // new Promise()时立即执行executor,并传入resolve和reject
  32. executor(_resolve, _reject)
  33. }
  34. // then方法,接收一个成功的回调和一个失败的回调
  35. then(resolveFn, rejectFn) {
  36. this._resolveQueue.push(resolveFn)
  37. this._rejectQueue.push(rejectFn)
  38. }
  39. }

3. then的链式调用

补充完规范,我们接着来实现链式调用,这是Promise实现的重点和难点,我们先来看一下then是如何链式调用的:

  1. const p1 = new Promise((resolve, reject) => {
  2. resolve(1)
  3. })
  4. p1
  5. .then(res => {
  6. console.log(res)
  7. //then回调中可以return一个Promise
  8. return new Promise((resolve, reject) => {
  9. setTimeout(() => {
  10. resolve(2)
  11. }, 1000);
  12. })
  13. })
  14. .then(res => {
  15. console.log(res)
  16. //then回调中也可以return一个值
  17. return 3
  18. })
  19. .then(res => {
  20. console.log(res)
  21. })

输出

  1. 1
  2. 2
  3. 3

我们思考一下如何实现这种链式调用:

  1. 显然.then()需要返回一个Promise,这样才能找到then方法,所以我们会把then方法的返回值包装成Promise。
  2. .then()的回调需要拿到上一个.then()的返回值
  3. .then()的回调需要顺序执行,以上面这段代码为例,虽然中间return了一个Promise,但执行顺序仍要保证是1->2->3。我们要等待当前Promise状态变更后,再执行下一个then收集的回调,这就要求我们对then的返回值分类讨论
    1. // then方法
    2. then(resolveFn, rejectFn) {
    3. //return一个新的promise
    4. return new MyPromise((resolve, reject) => {
    5. //把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论
    6. const fulfilledFn = value => {
    7. try {
    8. //执行第一个(当前的)Promise的成功回调,并获取返回值
    9. let x = resolveFn(value)
    10. //分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
    11. //这里resolve之后,就能被下一个.then()的回调获取到返回值,从而实现链式调用
    12. x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
    13. } catch (error) {
    14. reject(error)
    15. }
    16. }
    17. //把后续then收集的依赖都push进当前Promise的成功回调队列中(_rejectQueue), 这是为了保证顺序调用
    18. this._resolveQueue.push(fulfilledFn)
    19. //reject同理
    20. const rejectedFn = error => {
    21. try {
    22. let x = rejectFn(error)
    23. x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
    24. } catch (error) {
    25. reject(error)
    26. }
    27. }
    28. this._rejectQueue.push(rejectedFn)
    29. })
    30. }
    然后我们就能测试一下链式调用:
    1. const p1 = new MyPromise((resolve, reject) => {
    2. setTimeout(() => {
    3. resolve(1)
    4. }, 500);
    5. })
    6. p1
    7. .then(res => {
    8. console.log(res)
    9. return 2
    10. })
    11. .then(res => {
    12. console.log(res)
    13. return 3
    14. })
    15. .then(res => {
    16. console.log(res)
    17. })
    18. //输出 1 2 3

4.值穿透 & 状态已变更的情况

我们已经初步完成了链式调用,但是对于 then() 方法,我们还要两个细节需要处理一下

  1. 值穿透:根据规范,如果 then() 接收的参数不是function,那么我们应该忽略它。如果没有忽略,当then()回调不为function时将会抛出异常,导致链式调用中断
  2. 处理状态为resolve/reject的情况:其实我们上边 then() 的写法是对应状态为padding的情况,但是有些时候,resolve/reject 在 then() 之前就被执行(比如Promise.resolve().then()),如果这个时候还把then()回调push进resolve/reject的执行队列里,那么回调将不会被执行,因此对于状态已经变为fulfilledrejected的情况,我们直接执行then回调:

    1. // then方法,接收一个成功的回调和一个失败的回调
    2. then(resolveFn, rejectFn) {
    3. // 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行
    4. typeof resolveFn !== 'function' ? resolveFn = value => value : null
    5. typeof rejectFn !== 'function' ? rejectFn = reason => {
    6. throw new Error(reason instanceof Error? reason.message:reason);
    7. } : null
    8. // return一个新的promise
    9. return new MyPromise((resolve, reject) => {
    10. // 把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论
    11. const fulfilledFn = value => {
    12. try {
    13. // 执行第一个(当前的)Promise的成功回调,并获取返回值
    14. let x = resolveFn(value)
    15. // 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
    16. x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
    17. } catch (error) {
    18. reject(error)
    19. }
    20. }
    21. // reject同理
    22. const rejectedFn = error => {
    23. try {
    24. let x = rejectFn(error)
    25. x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
    26. } catch (error) {
    27. reject(error)
    28. }
    29. }
    30. switch (this._status) {
    31. // 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
    32. case PENDING:
    33. this._resolveQueue.push(fulfilledFn)
    34. this._rejectQueue.push(rejectedFn)
    35. break;
    36. // 当状态已经变为resolve/reject时,直接执行then回调
    37. case FULFILLED:
    38. fulfilledFn(this._value) // this._value是上一个then回调return的值(见完整版代码)
    39. break;
    40. case REJECTED:
    41. rejectedFn(this._value)
    42. break;
    43. }
    44. })
    45. }

5.兼容同步任务

完成了then的链式调用以后,我们再处理一个前边的细节,然后放出完整代码。上文我们说过,Promise的执行顺序是new Promise -> then()收集回调 -> resolve/reject执行回调,这一顺序是建立在executor是异步任务的前提上的,如果executor是一个同步任务,那么顺序就会变成new Promise -> resolve/reject执行回调 -> then()收集回调,resolve的执行跑到then之前去了,为了兼容这种情况,我们给resolve/reject执行回调的操作包一个setTimeout,让它异步执行。

这里插一句,有关这个setTimeout,其实还有一番学问。虽然规范没有要求回调应该被放进宏任务队列还是微任务队列,但其实Promise的默认实现是放进了微任务队列,我们的实现(包括大多数Promise手动实现和polyfill的转化)都是使用setTimeout放入了宏任务队列(当然我们也可以用MutationObserver模拟微任务)

  1. //Promise/A+规定的三种状态
  2. const PENDING = 'pending'
  3. const FULFILLED = 'fulfilled'
  4. const REJECTED = 'rejected'
  5. class MyPromise {
  6. // 构造方法接收一个回调
  7. constructor(executor) {
  8. this._status = PENDING // Promise状态
  9. this._value = undefined // 储存then回调return的值
  10. this._resolveQueue = [] // 成功队列, resolve时触发
  11. this._rejectQueue = [] // 失败队列, reject时触发
  12. // 由于resolve/reject是在executor内部被调用, 因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue
  13. let _resolve = (val) => {
  14. //把resolve执行回调的操作封装成一个函数,放进setTimeout里,以兼容executor是同步代码的情况
  15. const run = () => {
  16. if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected"
  17. this._status = FULFILLED // 变更状态
  18. this._value = val // 储存当前value
  19. // 这里之所以使用一个队列来储存回调,是为了实现规范要求的 "then 方法可以被同一个 promise 调用多次"
  20. // 如果使用一个变量而非队列来储存回调,那么即使多次p1.then()也只会执行一次回调
  21. while(this._resolveQueue.length) {
  22. const callback = this._resolveQueue.shift()
  23. callback(val)
  24. }
  25. }
  26. setTimeout(run)
  27. }
  28. // 实现同resolve
  29. let _reject = (val) => {
  30. const run = () => {
  31. if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected"
  32. this._status = REJECTED // 变更状态
  33. this._value = val // 储存当前value
  34. while(this._rejectQueue.length) {
  35. const callback = this._rejectQueue.shift()
  36. callback(val)
  37. }
  38. }
  39. setTimeout(run)
  40. }
  41. // new Promise()时立即执行executor,并传入resolve和reject
  42. executor(_resolve, _reject)
  43. }
  44. // then方法,接收一个成功的回调和一个失败的回调
  45. then(resolveFn, rejectFn) {
  46. // 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行
  47. typeof resolveFn !== 'function' ? resolveFn = value => value : null
  48. typeof rejectFn !== 'function' ? rejectFn = reason => {
  49. throw new Error(reason instanceof Error? reason.message:reason);
  50. } : null
  51. // return一个新的promise
  52. return new MyPromise((resolve, reject) => {
  53. // 把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论
  54. const fulfilledFn = value => {
  55. try {
  56. // 执行第一个(当前的)Promise的成功回调,并获取返回值
  57. let x = resolveFn(value)
  58. // 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
  59. x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
  60. } catch (error) {
  61. reject(error)
  62. }
  63. }
  64. // reject同理
  65. const rejectedFn = error => {
  66. try {
  67. let x = rejectFn(error)
  68. x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
  69. } catch (error) {
  70. reject(error)
  71. }
  72. }
  73. switch (this._status) {
  74. // 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
  75. case PENDING:
  76. this._resolveQueue.push(fulfilledFn)
  77. this._rejectQueue.push(rejectedFn)
  78. break;
  79. // 当状态已经变为resolve/reject时,直接执行then回调
  80. case FULFILLED:
  81. fulfilledFn(this._value) // this._value是上一个then回调return的值(见完整版代码)
  82. break;
  83. case REJECTED:
  84. rejectedFn(this._value)
  85. break;
  86. }
  87. })
  88. }
  89. }

然后我们可以测试一下这个Promise:

  1. const p1 = new MyPromise((resolve, reject) => {
  2. resolve(1) //同步executor测试
  3. })
  4. p1
  5. .then(res => {
  6. console.log(res)
  7. return 2 //链式调用测试
  8. })
  9. .then() //值穿透测试
  10. .then(res => {
  11. console.log(res)
  12. return new MyPromise((resolve, reject) => {
  13. resolve(3) //返回Promise测试
  14. })
  15. })
  16. .then(res => {
  17. console.log(res)
  18. throw new Error('reject测试') //reject测试
  19. })
  20. .then(() => {}, err => {
  21. console.log(err)
  22. })
  23. // 输出
  24. // 1
  25. // 2
  26. // 3
  27. // Error: reject测试
  28. 复制代码

到这里,我们已经实现了Promise的主要功能(`∀´)Ψ剩下的几个方法都非常简单,我们顺手收拾掉:

Promise.prototype.catch()

catch()方法返回一个Promise,并且处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected) 相同。

  1. //catch方法其实就是执行一下then的第二个回调
  2. catch(rejectFn) {
  3. return this.then(undefined, rejectFn)
  4. }

Promise.prototype.finally()

finally()方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。在finally之后,还可以继续then。并且会将值原封不动的传递给后面的then

  1. //finally方法
  2. finally(callback) {
  3. return this.then(
  4. value => MyPromise.resolve(callback()).then(() => value), // MyPromise.resolve执行回调,并在then中return结果传递给后面的Promise
  5. reason => MyPromise.resolve(callback()).then(() => { throw reason }) // reject同理
  6. )
  7. }

PS. 有同学问我MyPromise.resolve(callback())的意义,这里补充解释一下:这个写法其实涉及到一个finally()的使用细节,finally()如果return了一个reject状态的Promise,将会改变当前Promise的状态,这个MyPromise.resolve就用于改变Promise状态,在finally()没有返回reject态Promise或throw错误的情况下,去掉MyPromise.resolve也是一样的(欢迎大家向我提问,勘误的过程中也能很好地加深自己对Promise的理解,大家可以在各个交流群里直接@我)

参考资料:对 Promise.prototype.finally() 的粗浅理解

Promise.resolve()

Promise.resolve(value)方法返回一个以给定值解析后的Promise 对象。如果该值为promise,返回这个promise;如果这个值是thenable(即带有”then” 方法)),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;否则返回的promise将以此值完成。此函数将类promise对象的多层嵌套展平。

  1. //静态的resolve方法
  2. static resolve(value) {
  3. if(value instanceof MyPromise) return value // 根据规范, 如果参数是Promise实例, 直接return这个实例
  4. return new MyPromise(resolve => resolve(value))
  5. }

Promise.reject()

Promise.reject()方法返回一个带有拒绝原因的Promise对象。

  1. //静态的reject方法
  2. static reject(reason) {
  3. return new MyPromise((resolve, reject) => reject(reason))
  4. }

Promise.all()

Promise.all(iterable)方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。

  1. //静态的all方法
  2. static all(promiseArr) {
  3. let index = 0
  4. let result = []
  5. return new MyPromise((resolve, reject) => {
  6. promiseArr.forEach((p, i) => {
  7. //Promise.resolve(p)用于处理传入值不为Promise的情况
  8. MyPromise.resolve(p).then(
  9. val => {
  10. index++
  11. result[i] = val
  12. //所有then执行后, resolve结果
  13. if(index === promiseArr.length) {
  14. resolve(result)
  15. }
  16. },
  17. err => {
  18. //有一个Promise被reject时,MyPromise的状态变为reject
  19. reject(err)
  20. }
  21. )
  22. })
  23. })
  24. }

Promise.race()

Promise.race(iterable)方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

  1. static race(promiseArr) {
  2. return new MyPromise((resolve, reject) => {
  3. //同时执行Promise,如果有一个Promise的状态发生改变,就变更新MyPromise的状态
  4. for (let p of promiseArr) {
  5. MyPromise.resolve(p).then( //Promise.resolve(p)用于处理传入值不为Promise的情况
  6. value => {
  7. resolve(value) //注意这个resolve是上边new MyPromise的
  8. },
  9. err => {
  10. reject(err)
  11. }
  12. )
  13. }
  14. })
  15. }

完整代码

  1. //Promise/A+规定的三种状态
  2. const PENDING = 'pending'
  3. const FULFILLED = 'fulfilled'
  4. const REJECTED = 'rejected'
  5. class MyPromise {
  6. // 构造方法接收一个回调
  7. constructor(executor) {
  8. this._status = PENDING // Promise状态
  9. this._value = undefined // 储存then回调return的值
  10. this._resolveQueue = [] // 成功队列, resolve时触发
  11. this._rejectQueue = [] // 失败队列, reject时触发
  12. // 由于resolve/reject是在executor内部被调用, 因此需要使用箭头函数固定this指向, 否则找不到this._resolveQueue
  13. let _resolve = (val) => {
  14. //把resolve执行回调的操作封装成一个函数,放进setTimeout里,以兼容executor是同步代码的情况
  15. const run = () => {
  16. if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected"
  17. this._status = FULFILLED // 变更状态
  18. this._value = val // 储存当前value
  19. // 这里之所以使用一个队列来储存回调,是为了实现规范要求的 "then 方法可以被同一个 promise 调用多次"
  20. // 如果使用一个变量而非队列来储存回调,那么即使多次p1.then()也只会执行一次回调
  21. while(this._resolveQueue.length) {
  22. const callback = this._resolveQueue.shift()
  23. callback(val)
  24. }
  25. }
  26. setTimeout(run)
  27. }
  28. // 实现同resolve
  29. let _reject = (val) => {
  30. const run = () => {
  31. if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected"
  32. this._status = REJECTED // 变更状态
  33. this._value = val // 储存当前value
  34. while(this._rejectQueue.length) {
  35. const callback = this._rejectQueue.shift()
  36. callback(val)
  37. }
  38. }
  39. setTimeout(run)
  40. }
  41. // new Promise()时立即执行executor,并传入resolve和reject
  42. executor(_resolve, _reject)
  43. }
  44. // then方法,接收一个成功的回调和一个失败的回调
  45. then(resolveFn, rejectFn) {
  46. // 根据规范,如果then的参数不是function,则我们需要忽略它, 让链式调用继续往下执行
  47. typeof resolveFn !== 'function' ? resolveFn = value => value : null
  48. typeof rejectFn !== 'function' ? rejectFn = reason => {
  49. throw new Error(reason instanceof Error? reason.message:reason);
  50. } : null
  51. // return一个新的promise
  52. return new MyPromise((resolve, reject) => {
  53. // 把resolveFn重新包装一下,再push进resolve执行队列,这是为了能够获取回调的返回值进行分类讨论
  54. const fulfilledFn = value => {
  55. try {
  56. // 执行第一个(当前的)Promise的成功回调,并获取返回值
  57. let x = resolveFn(value)
  58. // 分类讨论返回值,如果是Promise,那么等待Promise状态变更,否则直接resolve
  59. x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
  60. } catch (error) {
  61. reject(error)
  62. }
  63. }
  64. // reject同理
  65. const rejectedFn = error => {
  66. try {
  67. let x = rejectFn(error)
  68. x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
  69. } catch (error) {
  70. reject(error)
  71. }
  72. }
  73. switch (this._status) {
  74. // 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行
  75. case PENDING:
  76. this._resolveQueue.push(fulfilledFn)
  77. this._rejectQueue.push(rejectedFn)
  78. break;
  79. // 当状态已经变为resolve/reject时,直接执行then回调
  80. case FULFILLED:
  81. fulfilledFn(this._value) // this._value是上一个then回调return的值(见完整版代码)
  82. break;
  83. case REJECTED:
  84. rejectedFn(this._value)
  85. break;
  86. }
  87. })
  88. }
  89. //catch方法其实就是执行一下then的第二个回调
  90. catch(rejectFn) {
  91. return this.then(undefined, rejectFn)
  92. }
  93. //finally方法
  94. finally(callback) {
  95. return this.then(
  96. value => MyPromise.resolve(callback()).then(() => value), //执行回调,并returnvalue传递给后面的then
  97. reason => MyPromise.resolve(callback()).then(() => { throw reason }) //reject同理
  98. )
  99. }
  100. //静态的resolve方法
  101. static resolve(value) {
  102. if(value instanceof MyPromise) return value //根据规范, 如果参数是Promise实例, 直接return这个实例
  103. return new MyPromise(resolve => resolve(value))
  104. }
  105. //静态的reject方法
  106. static reject(reason) {
  107. return new MyPromise((resolve, reject) => reject(reason))
  108. }
  109. //静态的all方法
  110. static all(promiseArr) {
  111. let index = 0
  112. let result = []
  113. return new MyPromise((resolve, reject) => {
  114. promiseArr.forEach((p, i) => {
  115. //Promise.resolve(p)用于处理传入值不为Promise的情况
  116. MyPromise.resolve(p).then(
  117. val => {
  118. index++
  119. result[i] = val
  120. if(index === promiseArr.length) {
  121. resolve(result)
  122. }
  123. },
  124. err => {
  125. reject(err)
  126. }
  127. )
  128. })
  129. })
  130. }
  131. //静态的race方法
  132. static race(promiseArr) {
  133. return new MyPromise((resolve, reject) => {
  134. //同时执行Promise,如果有一个Promise的状态发生改变,就变更新MyPromise的状态
  135. for (let p of promiseArr) {
  136. MyPromise.resolve(p).then( //Promise.resolve(p)用于处理传入值不为Promise的情况
  137. value => {
  138. resolve(value) //注意这个resolve是上边new MyPromise的
  139. },
  140. err => {
  141. reject(err)
  142. }
  143. )
  144. }
  145. })
  146. }
  147. }

洋洋洒洒150多行的代码,到这里,我们终于可以给Promise的实现做一个结尾了。我们从一个最简单的Promise使用实例开始,通过对调用流程的分析,根据观察者模式实现了Promise的大致骨架,然后依据Promise/A+规范填充代码,重点实现了then 的链式调用,最后完成了Promise的静态/实例方法。其实Promise实现在整体上并没有太复杂的思想,但我们日常使用的时候往往忽略了很多Promise细节,因而很难写出一个符合规范的Promise实现,源码的实现过程,其实也是对Promise使用细节重新学习的过程。

async/await实现

虽然前边花了这么多篇幅讲Promise的实现,不过探索async/await暂停执行的机制才是我们的初衷,下面我们就来进入这一块的内容。同样地,开头我们点一下async/await的使用意义。 在多个回调依赖的场景中,尽管Promise通过链式调用取代了回调嵌套,但过多的链式调用可读性仍然不佳,流程控制也不方便,ES7 提出的async 函数,终于让 JS 对于异步操作有了终极解决方案,简洁优美地解决了以上两个问题。

设想一个这样的场景,异步任务a->b->c之间存在依赖关系,如果我们通过then链式调用来处理这些关系,可读性并不是很好,如果我们想控制其中某个过程,比如在某些条件下,b不往下执行到c,那么也不是很方便控制

  1. Promise.resolve(a)
  2. .then(b => {
  3. // do something
  4. })
  5. .then(c => {
  6. // do something
  7. })

但是如果通过async/await来实现这个场景,可读性和流程控制都会方便不少。

  1. async () => {
  2. const a = await Promise.resolve(a);
  3. const b = await Promise.resolve(b);
  4. const c = await Promise.resolve(c);
  5. }

那么我们要如何实现一个async/await呢,首先我们要知道,async/await实际上是对Generator(生成器)的封装,是一个语法糖。由于Generator出现不久就被async/await取代了,很多同学对Generator比较陌生,因此我们先来看看Generator的用法:

ES6 新引入了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,通过next()方法可以切换到下一个状态,为改变执行流程提供了可能,从而为异步编程提供解决方案。

  1. function* myGenerator() {
  2. yield '1'
  3. yield '2'
  4. return '3'
  5. }
  6. const gen = myGenerator(); // 获取迭代器
  7. gen.next() //{value: "1", done: false}
  8. gen.next() //{value: "2", done: false}
  9. gen.next() //{value: "3", done: true}

也可以通过给next()传参, 让yield具有返回值

  1. function* myGenerator() {
  2. console.log(yield '1') //test1
  3. console.log(yield '2') //test2
  4. console.log(yield '3') //test3
  5. }
  6. // 获取迭代器
  7. const gen = myGenerator();
  8. gen.next()
  9. gen.next('test1')
  10. gen.next('test2')
  11. gen.next('test3')

我们看到Generator的用法,应该️会感到很熟悉,*/yieldasync/await看起来其实已经很相似了,它们都提供了暂停执行的功能,但二者又有三点不同:

  • async/await自带执行器,不需要手动调用next()就能自动执行下一步
  • async函数返回值是Promise对象,而Generator返回的是生成器对象
  • await能够返回Promise的resolve/reject的值

我们对async/await的实现,其实也就是对应以上三点封装Generator

1.自动执行

我们先来看一下,对于这样一个Generator,手动执行是怎样一个流程

  1. function* myGenerator() {
  2. yield Promise.resolve(1);
  3. yield Promise.resolve(2);
  4. yield Promise.resolve(3);
  5. }
  6. // 手动执行迭代器
  7. const gen = myGenerator()
  8. gen.next().value.then(val => {
  9. console.log(val)
  10. gen.next().value.then(val => {
  11. console.log(val)
  12. gen.next().value.then(val => {
  13. console.log(val)
  14. })
  15. })
  16. })
  17. //输出1 2 3

我们也可以通过给gen.next()传值的方式,让yield能返回resolve的值

  1. function* myGenerator() {
  2. console.log(yield Promise.resolve(1)) //1
  3. console.log(yield Promise.resolve(2)) //2
  4. console.log(yield Promise.resolve(3)) //3
  5. }
  6. // 手动执行迭代器
  7. const gen = myGenerator()
  8. gen.next().value.then(val => {
  9. // console.log(val)
  10. gen.next(val).value.then(val => {
  11. // console.log(val)
  12. gen.next(val).value.then(val => {
  13. // console.log(val)
  14. gen.next(val)
  15. })
  16. })
  17. })

显然,手动执行的写法看起来既笨拙又丑陋,我们希望生成器函数能自动往下执行,且yield能返回resolve的值,基于这两个需求,我们进行一个基本的封装,这里async/await是关键字,不能重写,我们用函数来模拟:

  1. function run(gen) {
  2. var g = gen() //由于每次gen()获取到的都是最新的迭代器,因此获取迭代器操作要放在_next()之前,否则会进入死循环
  3. function _next(val) { //封装一个方法, 递归执行g.next()
  4. var res = g.next(val) //获取迭代器对象,并返回resolve的值
  5. if(res.done) return res.value //递归终止条件
  6. res.value.then(val => { //Promise的then方法是实现自动迭代的前提
  7. _next(val) //等待Promise完成就自动执行下一个next,并传入resolve的值
  8. })
  9. }
  10. _next() //第一次执行
  11. }
  12. 复制代码

对于我们之前的例子,我们就能这样执行:

  1. function* myGenerator() {
  2. console.log(yield Promise.resolve(1)) //1
  3. console.log(yield Promise.resolve(2)) //2
  4. console.log(yield Promise.resolve(3)) //3
  5. }
  6. run(myGenerator)

这样我们就初步实现了一个async/await。上边的代码只有五六行,但并不是一下就能看明白的,我们之前用了四个例子来做铺垫,也是为了让读者更好地理解这段代码。 简单来说,我们封装了一个run方法,run方法里我们把执行下一步的操作封装成_next(),每次Promise.then()的时候都去执行_next(),实现自动迭代的效果。在迭代的过程中,我们还把resolve的值传入gen.next(),使得yield得以返回Promise的resolve的值

这里插一句,是不是只有.then方法这样的形式才能完成我们自动执行的功能呢?答案是否定的,yield后边除了接Promise,还可以接thunk函数,thunk函数不是一个新东西,所谓thunk函数,就是单参的只接受回调的函数,详细介绍可以看阮一峰Thunk 函数的含义和用法,无论是Promise还是thunk函数,其核心都是通过传入回调的方式来实现Generator的自动执行。thunk函数只作为一个拓展知识,理解有困难的同学也可以跳过这里,并不影响后续理解。

2.返回Promise & 异常处理

虽然我们实现了Generator的自动执行以及让yield返回resolve的值,但上边的代码还存在着几点问题:

  1. 需要兼容基本类型:这段代码能自动执行的前提是yield后面跟Promise,为了兼容后面跟着基本类型值的情况,我们需要把yield跟的内容(gen().next.value)都用Promise.resolve()转化一遍
  2. 缺少错误处理:上边代码里的Promise如果执行失败,就会导致后续执行直接中断,我们需要通过调用Generator.prototype.throw(),把错误抛出来,才能被外层的try-catch捕获到
  3. 返回值是Promiseasync/await的返回值是一个Promise,我们这里也需要保持一致,给返回值包一个Promise

我们改造一下run方法:

  1. function run(gen) {
  2. //把返回值包装成promise
  3. return new Promise((resolve, reject) => {
  4. var g = gen()
  5. function _next(val) {
  6. //错误处理
  7. try {
  8. var res = g.next(val)
  9. } catch(err) {
  10. return reject(err);
  11. }
  12. if(res.done) {
  13. return resolve(res.value);
  14. }
  15. //res.value包装为promise,以兼容yield后面跟基本类型的情况
  16. Promise.resolve(res.value).then(
  17. val => {
  18. _next(val);
  19. },
  20. err => {
  21. //抛出错误
  22. g.throw(err)
  23. });
  24. }
  25. _next();
  26. });
  27. }

然后我们可以测试一下:

  1. function* myGenerator() {
  2. try {
  3. console.log(yield Promise.resolve(1))
  4. console.log(yield 2) //2
  5. console.log(yield Promise.reject('error'))
  6. } catch (error) {
  7. console.log(error)
  8. }
  9. }
  10. const result = run(myGenerator) //result是一个Promise
  11. //输出 1 2 error

到这里,一个async/await的实现基本完成了。最后我们可以看一下babel对async/await的转换结果,其实整体的思路是一样的,但是写法稍有不同:

  1. //相当于我们的run()
  2. function _asyncToGenerator(fn) {
  3. // return一个function,和async保持一致。我们的run直接执行了Generator,其实是不太规范的
  4. return function() {
  5. var self = this
  6. var args = arguments
  7. return new Promise(function(resolve, reject) {
  8. var gen = fn.apply(self, args);
  9. //相当于我们的_next()
  10. function _next(value) {
  11. asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value);
  12. }
  13. //处理异常
  14. function _throw(err) {
  15. asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err);
  16. }
  17. _next(undefined);
  18. });
  19. };
  20. }
  21. function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
  22. try {
  23. var info = gen[key](arg);
  24. var value = info.value;
  25. } catch (error) {
  26. reject(error);
  27. return;
  28. }
  29. if (info.done) {
  30. resolve(value);
  31. } else {
  32. Promise.resolve(value).then(_next, _throw);
  33. }
  34. }

使用方式:

  1. const foo = _asyncToGenerator(function* () {
  2. try {
  3. console.log(yield Promise.resolve(1)) //1
  4. console.log(yield 2) //2
  5. return '3'
  6. } catch (error) {
  7. console.log(error)
  8. }
  9. })
  10. foo().then(res => {
  11. console.log(res) //3
  12. })

有关async/await的实现,到这里就告一段落了。但是直到结尾,我们也不知道await到底是如何暂停执行的,有关await暂停执行的秘密,我们还要到Generator的实现中去寻找答案

Generator实现

我们从一个简单的Generator使用实例开始,一步步探究Generator的实现原理:

  1. function* foo() {
  2. yield 'result1'
  3. yield 'result2'
  4. yield 'result3'
  5. }
  6. const gen = foo()
  7. console.log(gen.next().value)
  8. console.log(gen.next().value)
  9. console.log(gen.next().value)

我们可以在babel官网上在线转化这段代码,看看ES5环境下是如何实现Generator的:

  1. "use strict";
  2. var _marked =
  3. /*#__PURE__*/
  4. regeneratorRuntime.mark(foo);
  5. function foo() {
  6. return regeneratorRuntime.wrap(function foo$(_context) {
  7. while (1) {
  8. switch (_context.prev = _context.next) {
  9. case 0:
  10. _context.next = 2;
  11. return 'result1';
  12. case 2:
  13. _context.next = 4;
  14. return 'result2';
  15. case 4:
  16. _context.next = 6;
  17. return 'result3';
  18. case 6:
  19. case "end":
  20. return _context.stop();
  21. }
  22. }
  23. }, _marked);
  24. }
  25. var gen = foo();
  26. console.log(gen.next().value);
  27. console.log(gen.next().value);
  28. console.log(gen.next().value);

代码咋一看不长,但如果仔细观察会发现有两个不认识的东西 —— regeneratorRuntime.markregeneratorRuntime.wrap,这两者其实是 regenerator-runtime 模块里的两个方法,regenerator-runtime 模块来自facebook的 regenerator 模块,完整代码在runtime.js,这个runtime有700多行…-_-||,因此我们不能全讲,不太重要的部分我们就简单地过一下,重点讲解暂停执行相关部分代码

个人觉得啃源码的效果不是很好,建议读者拉到末尾先看结论和简略版实现,源码作为一个补充理解

regeneratorRuntime.mark()

regeneratorRuntime.mark(foo)这个方法在第一行被调用,我们先看一下runtime里mark()方法的定义

  1. //runtime.js里的定义稍有不同,多了一些判断,以下是编译后的代码
  2. runtime.mark = function(genFun) {
  3. genFun.__proto__ = GeneratorFunctionPrototype;
  4. genFun.prototype = Object.create(Gp);
  5. return genFun;
  6. };

这里边GeneratorFunctionPrototypeGp我们都不认识,他们被定义在runtime里,不过没关系,我们只要知道mark()方法为生成器函数(foo)绑定了一系列原型就可以了,这里就简单地过了

regeneratorRuntime.wrap()

从上面babel转化的代码我们能看到,执行foo(),其实就是执行wrap(),那么这个方法起到什么作用呢,他想包装一个什么东西呢,我们先来看看wrap方法的定义:

  1. //runtime.js里的定义稍有不同,多了一些判断,以下是编译后的代码
  2. function wrap(innerFn, outerFn, self) {
  3. var generator = Object.create(outerFn.prototype);
  4. var context = new Context([]);
  5. generator._invoke = makeInvokeMethod(innerFn, self, context);
  6. return generator;
  7. }

wrap方法先是创建了一个generator,并继承outerFn.prototype;然后new了一个context对象makeInvokeMethod方法接收innerFn(对应foo$)contextthis,并把返回值挂到generator._invoke上;最后return了generator。其实wrap()相当于是给generator增加了一个_invoke方法
这段代码肯定让人产生很多疑问,outerFn.prototype是什么,Context又是什么,makeInvokeMethod又做了哪些操作。下面我们就来一一解答:

outerFn.prototype其实就是genFun.prototype

这个我们结合一下上面的代码就能知道

context可以直接理解为这样一个全局对象,用于储存各种状态和上下文:

  1. var ContinueSentinel = {};
  2. var context = {
  3. done: false,
  4. method: "next",
  5. next: 0,
  6. prev: 0,
  7. abrupt: function(type, arg) {
  8. var record = {};
  9. record.type = type;
  10. record.arg = arg;
  11. return this.complete(record);
  12. },
  13. complete: function(record, afterLoc) {
  14. if (record.type === "return") {
  15. this.rval = this.arg = record.arg;
  16. this.method = "return";
  17. this.next = "end";
  18. }
  19. return ContinueSentinel;
  20. },
  21. stop: function() {
  22. this.done = true;
  23. return this.rval;
  24. }
  25. };

makeInvokeMethod的定义如下,它return了一个invoke方法,invoke用于判断当前状态和执行下一步,其实就是我们调用的next()

  1. //以下是编译后的代码
  2. function makeInvokeMethod(innerFn, context) {
  3. // 将状态置为start
  4. var state = "start";
  5. return function invoke(method, arg) {
  6. // 已完成
  7. if (state === "completed") {
  8. return { value: undefined, done: true };
  9. }
  10. context.method = method;
  11. context.arg = arg;
  12. // 执行中
  13. while (true) {
  14. state = "executing";
  15. var record = {
  16. type: "normal",
  17. arg: innerFn.call(self, context) // 执行下一步,并获取状态(其实就是switch里边return的值)
  18. };
  19. if (record.type === "normal") {
  20. // 判断是否已经执行完成
  21. state = context.done ? "completed" : "yield";
  22. // ContinueSentinel其实是一个空对象,record.arg === {}则跳过return进入下一个循环
  23. // 什么时候record.arg会为空对象呢, 答案是没有后续yield语句或已经return的时候,也就是switch返回了空值的情况(跟着上面的switch走一下就知道了)
  24. if (record.arg === ContinueSentinel) {
  25. continue;
  26. }
  27. // next()的返回值
  28. return {
  29. value: record.arg,
  30. done: context.done
  31. };
  32. }
  33. }
  34. };
  35. }

为什么generator._invoke实际上就是gen.next呢,因为在runtime对于next()的定义中,next()其实就return了_invoke方法

  1. // Helper for defining the .next, .throw, and .return methods of the
  2. // Iterator interface in terms of a single ._invoke method.
  3. function defineIteratorMethods(prototype) {
  4. ["next", "throw", "return"].forEach(function(method) {
  5. prototype[method] = function(arg) {
  6. return this._invoke(method, arg);
  7. };
  8. });
  9. }
  10. defineIteratorMethods(Gp);

低配实现 & 调用流程分析

这么一遍源码下来,估计很多读者还是懵逼的,毕竟源码中纠集了很多概念和封装,一时半会不好完全理解,让我们跳出源码,实现一个简单的Generator,然后再回过头看源码,会得到更清晰的认识

  1. // 生成器函数根据yield语句将代码分割为switch-case块,后续通过切换_context.prev和_context.next来分别执行各个case
  2. function gen$(_context) {
  3. while (1) {
  4. switch (_context.prev = _context.next) {
  5. case 0:
  6. _context.next = 2;
  7. return 'result1';
  8. case 2:
  9. _context.next = 4;
  10. return 'result2';
  11. case 4:
  12. _context.next = 6;
  13. return 'result3';
  14. case 6:
  15. case "end":
  16. return _context.stop();
  17. }
  18. }
  19. }
  20. // 低配版context
  21. var context = {
  22. next:0,
  23. prev: 0,
  24. done: false,
  25. stop: function stop () {
  26. this.done = true
  27. }
  28. }
  29. // 低配版invoke
  30. let gen = function() {
  31. return {
  32. next: function() {
  33. value = context.done ? undefined: gen$(context)
  34. done = context.done
  35. return {
  36. value,
  37. done
  38. }
  39. }
  40. }
  41. }
  42. // 测试使用
  43. var g = gen()
  44. g.next() // {value: "result1", done: false}
  45. g.next() // {value: "result2", done: false}
  46. g.next() // {value: "result3", done: false}
  47. g.next() // {value: undefined, done: true}

这段代码并不难理解,我们分析一下调用流程:

  1. 我们定义的function*生成器函数被转化为以上代码
  2. 转化后的代码分为三大块:
    • gen$(_context)由yield分割生成器函数代码而来
    • context对象用于储存函数执行上下文
    • invoke()方法定义next(),用于执行gen$(_context)来跳到下一步
  3. 当我们调用g.next(),就相当于调用invoke()方法,执行gen$(_context),进入switch语句,switch根据context的标识,执行对应的case块,return对应结果
  4. 当生成器函数运行到末尾(没有下一个yield或已经return),switch匹配不到对应代码块,就会return空值,这时g.next()返回{value: undefined, done: true}

从中我们可以看出,Generator实现的核心在于上下文的保存,函数并没有真的被挂起,每一次yield,其实都执行了一遍传入的生成器函数,只是在这个过程中间用了一个context对象储存上下文,使得每次执行生成器函数的时候,都可以从上一个执行结果开始执行,看起来就像函数被挂起了一样