笔者刚接触async/await
时,就被其暂停执行的特性吸引了,心想在没有原生API支持的状况下,await竟然能挂起当前方法,实现暂停执行,我感到十分好奇。好奇心驱使我一层一层剥开有关JS异步编程的一切。阅读完本文,读者应该可以了解:react
Promise
的实现原理async/await
的实现原理Generator
的实现原理在成文过程当中,笔者查阅了不少讲解Promise实现的文章,但感受大多文章都很难称得上条理清晰,有的上来就放大段Promise规范翻译,有的在Promise基础使用上浪费篇幅,又或者把一个简单的东西长篇大论,过分讲解,我推荐头铁的同窗直接拉到本章小结看最终实现,结合着注释直接啃代码也能理解十之八九git
回归正题,文章开头咱们先点一下Promise为咱们解决了什么问题:在传统的异步编程中,若是异步之间存在依赖关系,咱们就须要经过层层嵌套回调来知足这种依赖,若是嵌套层数过多,可读性和可维护性都变得不好,产生所谓“回调地狱”,而Promise将回调嵌套改成链式调用,增长可读性和可维护性。下面咱们就来一步步实现一个Promise:github
咱们先来看一个最简单的Promise使用:编程
const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('result') }, 1000); }) 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的大体形状:promise
class MyPromise { // 构造方法接收一个回调 constructor(executor) { this._resolveQueue = [] // then收集的执行成功的回调队列 this._rejectQueue = [] // then收集的执行失败的回调队列 // 因为resolve/reject是在executor内部被调用, 所以须要使用箭头函数固定this指向, 不然找不到this._resolveQueue let _resolve = (val) => { // 从成功队列里取出回调依次执行 while(this._resolveQueue.length) { const callback = this._resolveQueue.shift() callback(val) } } // 实现同resolve let _reject = (val) => { while(this._rejectQueue.length) { const callback = this._rejectQueue.shift() callback(val) } } // new Promise()时当即执行executor,并传入resolve和reject executor(_resolve, _reject) } // then方法,接收一个成功的回调和一个失败的回调,并push进对应队列 then(resolveFn, rejectFn) { this._resolveQueue.push(resolveFn) this._rejectQueue.push(rejectFn) } } 复制代码
写完代码咱们能够测试一下:babel
const p1 = new MyPromise((resolve, reject) => { setTimeout(() => { resolve('result') }, 1000); }) p1.then(res => console.log(res)) //一秒后输出result 复制代码
咱们运用观察者模式简单的实现了一下then
和resolve
,使咱们可以在then方法的回调里取得异步操做的返回值,但咱们这个Promise离最终实现还有很长的距离,下面咱们来一步步补充这个Promise:app
上面咱们已经简单地实现了一个超低配版Promise,但咱们会看到不少文章和咱们写的不同,他们的Promise实现中还引入了各类状态控制,这是因为ES6的Promise实现须要遵循Promise/A+规范,是规范对Promise的状态控制作了要求。Promise/A+的规范比较长,这里只总结两条核心规则:异步
- Promise本质是一个状态机,且状态只能为如下三种:
Pending(等待态)
、Fulfilled(执行态)
、Rejected(拒绝态)
,状态的变动是单向的,只能从Pending -> Fulfilled 或 Pending -> Rejected,状态变动不可逆then方法
接收两个可选参数,分别对应状态改变时触发的回调。then方法返回一个promise。then 方法能够被同一个 promise 调用屡次。
根据规范,咱们补充一下Promise的代码:async
//Promise/A+规范的三种状态 const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class MyPromise { // 构造方法接收一个回调 constructor(executor) { this._status = PENDING // Promise状态 this._resolveQueue = [] // 成功队列, resolve时触发 this._rejectQueue = [] // 失败队列, reject时触发 // 因为resolve/reject是在executor内部被调用, 所以须要使用箭头函数固定this指向, 不然找不到this._resolveQueue let _resolve = (val) => { if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected" this._status = FULFILLED // 变动状态 // 这里之因此使用一个队列来储存回调,是为了实现规范要求的 "then 方法能够被同一个 promise 调用屡次" // 若是使用一个变量而非队列来储存回调,那么即便屡次p1.then()也只会执行一次回调 while(this._resolveQueue.length) { const callback = this._resolveQueue.shift() callback(val) } } // 实现同resolve let _reject = (val) => { if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected" this._status = REJECTED // 变动状态 while(this._rejectQueue.length) { const callback = this._rejectQueue.shift() callback(val) } } // new Promise()时当即执行executor,并传入resolve和reject executor(_resolve, _reject) } // then方法,接收一个成功的回调和一个失败的回调 then(resolveFn, rejectFn) { this._resolveQueue.push(resolveFn) this._rejectQueue.push(rejectFn) } } 复制代码
补充完规范,咱们接着来实现链式调用,这是Promise实现的重点和难点,咱们先来看一下then是如何链式调用的:
const p1 = new Promise((resolve, reject) => { resolve(1) }) p1 .then(res => { console.log(res) //then回调中能够return一个Promise return new Promise((resolve, reject) => { setTimeout(() => { resolve(2) }, 1000); }) }) .then(res => { console.log(res) //then回调中也能够return一个值 return 3 }) .then(res => { console.log(res) }) 复制代码
输出
1 2 3 复制代码
咱们思考一下如何实现这种链式调用:
.then()
须要返回一个Promise,这样才能找到then方法,因此咱们会把then方法的返回值包装成Promise。.then()
的回调须要顺序执行,以上面这段代码为例,虽然中间return了一个Promise,但执行顺序仍要保证是1->2->3。咱们要等待当前Promise状态变动后,再执行下一个then收集的回调,这就要求咱们对then的返回值分类讨论// then方法 then(resolveFn, rejectFn) { //return一个新的promise return new MyPromise((resolve, reject) => { //把resolveFn从新包装一下,再push进resolve执行队列,这是为了可以获取回调的返回值进行分类讨论 const fulfilledFn = value => { try { //执行第一个(当前的)Promise的成功回调,并获取返回值 let x = resolveFn(value) //分类讨论返回值,若是是Promise,那么等待Promise状态变动,不然直接resolve x instanceof MyPromise ? x.then(resolve, reject) : resolve(x) } catch (error) { reject(error) } } //把后续then收集的依赖都push进当前Promise的成功回调队列中(_rejectQueue), 这是为了保证顺序调用 this._resolveQueue.push(fulfilledFn) //reject同理 const rejectedFn = error => { try { let x = rejectFn(error) x instanceof MyPromise ? x.then(resolve, reject) : resolve(x) } catch (error) { reject(error) } } this._rejectQueue.push(rejectedFn) }) } 复制代码
而后咱们就能测试一下链式调用:
const p1 = new MyPromise((resolve, reject) => { setTimeout(() => { resolve(1) }, 500); }) p1 .then(res => { console.log(res) return 2 }) .then(res => { console.log(res) return 3 }) .then(res => { console.log(res) }) //输出 1 2 3 复制代码
咱们已经初步完成了链式调用,可是对于 then() 方法,咱们还要两个细节须要处理一下
padding
的状况,可是有些时候,resolve/reject 在 then() 以前就被执行(好比Promise.resolve().then()
),若是这个时候还把then()回调push进resolve/reject的执行队列里,那么回调将不会被执行,所以对于状态已经变为fulfilled
或rejected
的状况,咱们直接执行then回调:// then方法,接收一个成功的回调和一个失败的回调 then(resolveFn, rejectFn) { // 根据规范,若是then的参数不是function,则咱们须要忽略它, 让链式调用继续往下执行 typeof resolveFn !== 'function' ? resolveFn = value => value : null typeof rejectFn !== 'function' ? rejectFn = reason => { throw new Error(reason instanceof Error? reason.message:reason); } : null // return一个新的promise return new MyPromise((resolve, reject) => { // 把resolveFn从新包装一下,再push进resolve执行队列,这是为了可以获取回调的返回值进行分类讨论 const fulfilledFn = value => { try { // 执行第一个(当前的)Promise的成功回调,并获取返回值 let x = resolveFn(value) // 分类讨论返回值,若是是Promise,那么等待Promise状态变动,不然直接resolve x instanceof MyPromise ? x.then(resolve, reject) : resolve(x) } catch (error) { reject(error) } } // reject同理 const rejectedFn = error => { try { let x = rejectFn(error) x instanceof MyPromise ? x.then(resolve, reject) : resolve(x) } catch (error) { reject(error) } } switch (this._status) { // 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行 case PENDING: this._resolveQueue.push(fulfilledFn) this._rejectQueue.push(rejectedFn) break; // 当状态已经变为resolve/reject时,直接执行then回调 case FULFILLED: fulfilledFn(this._value) // this._value是上一个then回调return的值(见完整版代码) break; case REJECTED: rejectedFn(this._value) break; } }) } 复制代码
完成了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模拟微任务)
//Promise/A+规定的三种状态 const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class MyPromise { // 构造方法接收一个回调 constructor(executor) { this._status = PENDING // Promise状态 this._value = undefined // 储存then回调return的值 this._resolveQueue = [] // 成功队列, resolve时触发 this._rejectQueue = [] // 失败队列, reject时触发 // 因为resolve/reject是在executor内部被调用, 所以须要使用箭头函数固定this指向, 不然找不到this._resolveQueue let _resolve = (val) => { //把resolve执行回调的操做封装成一个函数,放进setTimeout里,以兼容executor是同步代码的状况 const run = () => { if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected" this._status = FULFILLED // 变动状态 this._value = val // 储存当前value // 这里之因此使用一个队列来储存回调,是为了实现规范要求的 "then 方法能够被同一个 promise 调用屡次" // 若是使用一个变量而非队列来储存回调,那么即便屡次p1.then()也只会执行一次回调 while(this._resolveQueue.length) { const callback = this._resolveQueue.shift() callback(val) } } setTimeout(run) } // 实现同resolve let _reject = (val) => { const run = () => { if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected" this._status = REJECTED // 变动状态 this._value = val // 储存当前value while(this._rejectQueue.length) { const callback = this._rejectQueue.shift() callback(val) } } setTimeout(run) } // new Promise()时当即执行executor,并传入resolve和reject executor(_resolve, _reject) } // then方法,接收一个成功的回调和一个失败的回调 then(resolveFn, rejectFn) { // 根据规范,若是then的参数不是function,则咱们须要忽略它, 让链式调用继续往下执行 typeof resolveFn !== 'function' ? resolveFn = value => value : null typeof rejectFn !== 'function' ? rejectFn = reason => { throw new Error(reason instanceof Error? reason.message:reason); } : null // return一个新的promise return new MyPromise((resolve, reject) => { // 把resolveFn从新包装一下,再push进resolve执行队列,这是为了可以获取回调的返回值进行分类讨论 const fulfilledFn = value => { try { // 执行第一个(当前的)Promise的成功回调,并获取返回值 let x = resolveFn(value) // 分类讨论返回值,若是是Promise,那么等待Promise状态变动,不然直接resolve x instanceof MyPromise ? x.then(resolve, reject) : resolve(x) } catch (error) { reject(error) } } // reject同理 const rejectedFn = error => { try { let x = rejectFn(error) x instanceof MyPromise ? x.then(resolve, reject) : resolve(x) } catch (error) { reject(error) } } switch (this._status) { // 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行 case PENDING: this._resolveQueue.push(fulfilledFn) this._rejectQueue.push(rejectedFn) break; // 当状态已经变为resolve/reject时,直接执行then回调 case FULFILLED: fulfilledFn(this._value) // this._value是上一个then回调return的值(见完整版代码) break; case REJECTED: rejectedFn(this._value) break; } }) } } 复制代码
而后咱们能够测试一下这个Promise:
const p1 = new MyPromise((resolve, reject) => { resolve(1) //同步executor测试 }) p1 .then(res => { console.log(res) return 2 //链式调用测试 }) .then() //值穿透测试 .then(res => { console.log(res) return new MyPromise((resolve, reject) => { resolve(3) //返回Promise测试 }) }) .then(res => { console.log(res) throw new Error('reject测试') //reject测试 }) .then(() => {}, err => { console.log(err) }) // 输出 // 1 // 2 // 3 // Error: reject测试 复制代码
到这里,咱们已经实现了Promise的主要功能(`∀´)Ψ
剩下的几个方法都很是简单,咱们顺手收拾掉:
catch()方法
返回一个Promise,而且处理拒绝的状况。它的行为与调用Promise.prototype.then(undefined, onRejected) 相同。
//catch方法其实就是执行一下then的第二个回调 catch(rejectFn) { return this.then(undefined, rejectFn) } 复制代码
finally()方法
返回一个Promise。在promise结束时,不管结果是fulfilled或者是rejected,都会执行指定的回调函数。在finally以后,还能够继续then。而且会将值原封不动的传递给后面的then
//finally方法 finally(callback) { return this.then( value => MyPromise.resolve(callback()).then(() => value), // MyPromise.resolve执行回调,并在then中return结果传递给后面的Promise reason => MyPromise.resolve(callback()).then(() => { throw reason }) // reject同理 ) } 复制代码
Promise.resolve(value)
方法返回一个以给定值解析后的Promise 对象。若是该值为promise,返回这个promise;若是这个值是thenable(即带有"then" 方法)),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;不然返回的promise将以此值完成。此函数将类promise对象的多层嵌套展平。
//静态的resolve方法 static resolve(value) { if(value instanceof MyPromise) return value // 根据规范, 若是参数是Promise实例, 直接return这个实例 return new MyPromise(resolve => resolve(value)) } 复制代码
Promise.reject()
方法返回一个带有拒绝缘由的Promise对象。
//静态的reject方法 static reject(reason) { return new MyPromise((resolve, reject) => reject(reason)) } 复制代码
Promise.all(iterable)
方法返回一个 Promise 实例,此实例在 iterable 参数内全部的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);若是参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败缘由的是第一个失败 promise 的结果。
//静态的all方法 static all(promiseArr) { let index = 0 let result = [] return new MyPromise((resolve, reject) => { promiseArr.forEach((p, i) => { //Promise.resolve(p)用于处理传入值不为Promise的状况 MyPromise.resolve(p).then( val => { index++ result[i] = val //全部then执行后, resolve结果 if(index === promiseArr.length) { resolve(result) } }, err => { //有一个Promise被reject时,MyPromise的状态变为reject reject(err) } ) }) }) } 复制代码
Promise.race(iterable)
方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
static race(promiseArr) { return new MyPromise((resolve, reject) => { //同时执行Promise,若是有一个Promise的状态发生改变,就变动新MyPromise的状态 for (let p of promiseArr) { MyPromise.resolve(p).then( //Promise.resolve(p)用于处理传入值不为Promise的状况 value => { resolve(value) //注意这个resolve是上边new MyPromise的 }, err => { reject(err) } ) } }) } 复制代码
//Promise/A+规定的三种状态 const PENDING = 'pending' const FULFILLED = 'fulfilled' const REJECTED = 'rejected' class MyPromise { // 构造方法接收一个回调 constructor(executor) { this._status = PENDING // Promise状态 this._value = undefined // 储存then回调return的值 this._resolveQueue = [] // 成功队列, resolve时触发 this._rejectQueue = [] // 失败队列, reject时触发 // 因为resolve/reject是在executor内部被调用, 所以须要使用箭头函数固定this指向, 不然找不到this._resolveQueue let _resolve = (val) => { //把resolve执行回调的操做封装成一个函数,放进setTimeout里,以兼容executor是同步代码的状况 const run = () => { if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected" this._status = FULFILLED // 变动状态 this._value = val // 储存当前value // 这里之因此使用一个队列来储存回调,是为了实现规范要求的 "then 方法能够被同一个 promise 调用屡次" // 若是使用一个变量而非队列来储存回调,那么即便屡次p1.then()也只会执行一次回调 while(this._resolveQueue.length) { const callback = this._resolveQueue.shift() callback(val) } } setTimeout(run) } // 实现同resolve let _reject = (val) => { const run = () => { if(this._status !== PENDING) return // 对应规范中的"状态只能由pending到fulfilled或rejected" this._status = REJECTED // 变动状态 this._value = val // 储存当前value while(this._rejectQueue.length) { const callback = this._rejectQueue.shift() callback(val) } } setTimeout(run) } // new Promise()时当即执行executor,并传入resolve和reject executor(_resolve, _reject) } // then方法,接收一个成功的回调和一个失败的回调 then(resolveFn, rejectFn) { // 根据规范,若是then的参数不是function,则咱们须要忽略它, 让链式调用继续往下执行 typeof resolveFn !== 'function' ? resolveFn = value => value : null typeof rejectFn !== 'function' ? rejectFn = reason => { throw new Error(reason instanceof Error? reason.message:reason); } : null // return一个新的promise return new MyPromise((resolve, reject) => { // 把resolveFn从新包装一下,再push进resolve执行队列,这是为了可以获取回调的返回值进行分类讨论 const fulfilledFn = value => { try { // 执行第一个(当前的)Promise的成功回调,并获取返回值 let x = resolveFn(value) // 分类讨论返回值,若是是Promise,那么等待Promise状态变动,不然直接resolve x instanceof MyPromise ? x.then(resolve, reject) : resolve(x) } catch (error) { reject(error) } } // reject同理 const rejectedFn = error => { try { let x = rejectFn(error) x instanceof MyPromise ? x.then(resolve, reject) : resolve(x) } catch (error) { reject(error) } } switch (this._status) { // 当状态为pending时,把then回调push进resolve/reject执行队列,等待执行 case PENDING: this._resolveQueue.push(fulfilledFn) this._rejectQueue.push(rejectedFn) break; // 当状态已经变为resolve/reject时,直接执行then回调 case FULFILLED: fulfilledFn(this._value) // this._value是上一个then回调return的值(见完整版代码) break; case REJECTED: rejectedFn(this._value) break; } }) } //catch方法其实就是执行一下then的第二个回调 catch(rejectFn) { return this.then(undefined, rejectFn) } //finally方法 finally(callback) { return this.then( value => MyPromise.resolve(callback()).then(() => value), //执行回调,并returnvalue传递给后面的then reason => MyPromise.resolve(callback()).then(() => { throw reason }) //reject同理 ) } //静态的resolve方法 static resolve(value) { if(value instanceof MyPromise) return value //根据规范, 若是参数是Promise实例, 直接return这个实例 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.resolve(p)用于处理传入值不为Promise的状况 MyPromise.resolve(p).then( val => { index++ result[i] = val 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的状况 value => { resolve(value) //注意这个resolve是上边new MyPromise的 }, err => { reject(err) } ) } }) } } 复制代码
洋洋洒洒150多行的代码,到这里,咱们终于能够给Promise的实现作一个结尾了。咱们从一个最简单的Promise使用实例开始,经过对调用流程的分析,根据观察者模式实现了Promise的大体骨架,而后依据Promise/A+规范填充代码,重点实现了then 的链式调用,最后完成了Promise的静态/实例方法。其实Promise实如今总体上并无太复杂的思想,但咱们平常使用的时候每每忽略了不少Promise细节,于是很难写出一个符合规范的Promise实现,源码的实现过程,其实也是对Promise使用细节从新学习的过程。
虽然前边花了这么多篇幅讲Promise的实现,不过探索async/await
暂停执行的机制才是咱们的初衷,下面咱们就来进入这一块的内容。一样地,开头咱们点一下async/await的使用意义。 在多个回调依赖的场景中,尽管Promise经过链式调用取代了回调嵌套,但过多的链式调用可读性仍然不佳,流程控制也不方便,ES7 提出的async 函数,终于让 JS 对于异步操做有了终极解决方案,简洁优美地解决了以上两个问题。
设想一个这样的场景,异步任务a->b->c之间存在依赖关系,若是咱们经过then链式调用来处理这些关系,可读性并非很好,若是咱们想控制其中某个过程,好比在某些条件下,b不往下执行到c,那么也不是很方便控制
Promise.resolve(a) .then(b => { // do something }) .then(c => { // do something }) 复制代码
可是若是经过async/await来实现这个场景,可读性和流程控制都会方便很多。
async () => { const a = await Promise.resolve(a); const b = await Promise.resolve(b); const c = await Promise.resolve(c); } 复制代码
那么咱们要如何实现一个async/await呢,首先咱们要知道,async/await其实是对Generator(生成器)的封装,是一个语法糖。因为Generator出现不久就被async/await取代了,不少同窗对Generator比较陌生,所以咱们先来看看Generator的用法:
ES6 新引入了 Generator 函数,能够经过 yield 关键字,把函数的执行流挂起,经过next()方法能够切换到下一个状态,为改变执行流程提供了可能,从而为异步编程提供解决方案。
function* myGenerator() { yield '1' yield '2' return '3' } const gen = myGenerator(); // 获取迭代器 gen.next() //{value: "1", done: false} gen.next() //{value: "2", done: false} gen.next() //{value: "3", done: true} 复制代码
也能够经过给next()
传参, 让yield具备返回值
function* myGenerator() { console.log(yield '1') //test1 console.log(yield '2') //test2 console.log(yield '3') //test3 } // 获取迭代器 const gen = myGenerator(); gen.next() gen.next('test1') gen.next('test2') gen.next('test3') 复制代码
咱们看到Generator的用法,应该️会感到很熟悉,*/yield
和async/await
看起来其实已经很类似了,它们都提供了暂停执行的功能,但两者又有三点不一样:
async/await
自带执行器,不须要手动调用next()就能自动执行下一步async
函数返回值是Promise对象,而Generator返回的是生成器对象await
可以返回Promise的resolve/reject的值咱们对async/await的实现,其实也就是对应以上三点封装Generator
咱们先来看一下,对于这样一个Generator,手动执行是怎样一个流程
function* myGenerator() { yield Promise.resolve(1); yield Promise.resolve(2); yield Promise.resolve(3); } const gen = myGenerator() gen.next().value.then(val => { console.log(val) gen.next().value.then(val => { console.log(val) gen.next().value.then(val => { console.log(val) }) }) }) //输出1 2 3 复制代码
咱们也能够经过给gen.next()
传值的方式,让yield能返回resolve的值
function* myGenerator() { console.log(yield Promise.resolve(1)) //1 console.log(yield Promise.resolve(2)) //2 console.log(yield Promise.resolve(3)) //3 } const gen = myGenerator() gen.next().value.then(val => { // console.log(val) gen.next(val).value.then(val => { // console.log(val) gen.next(val).value.then(val => { // console.log(val) gen.next(val) }) }) }) 复制代码
显然,手动执行的写法看起来既笨拙又丑陋,咱们但愿生成器函数能自动往下执行,且yield能返回resolve的值,基于这两个需求,咱们进行一个基本的封装,这里async/await
是关键字,不能重写,咱们用函数来模拟:
function run(gen) { var g = gen() //因为每次gen()获取到的都是最新的迭代器,所以获取迭代器操做要放在step()以前,不然会进入死循环 function step(val) { //封装一个方法, 递归执行next() var res = g.next(val) //获取迭代器对象,并返回resolve的值 if(res.done) return res.value //递归终止条件 res.value.then(val => { //Promise的then方法是实现自动迭代的前提 step(val) //等待Promise完成就自动执行下一个next,并传入resolve的值 }) } step() //第一次执行 } 复制代码
对于咱们以前的例子,咱们就能这样执行:
function* myGenerator() { console.log(yield Promise.resolve(1)) //1 console.log(yield Promise.resolve(2)) //2 console.log(yield Promise.resolve(3)) //3 } run(myGenerator) 复制代码
这样咱们就初步实现了一个async/await
上边的代码只有五六行,但并非一下就能看明白的,咱们以前用了四个例子来作铺垫,也是为了让读者更好地理解这段代码。 简单的说,咱们封装了一个run方法,run方法里咱们把执行下一步的操做封装成step(),每次Promise.then()的时候都去执行step(),实现自动迭代的效果。在迭代的过程当中,咱们还把resolve的值传入gen.next()
,使得yield得以返回Promise的resolve的值
这里插一句,是否是只有.then方法
这样的形式才能完成咱们自动执行的功能呢?答案是否认的,yield后边除了接Promise,还能够接thunk函数
,thunk函数不是一个新东西,所谓thunk函数,就是 单参的只接受回调的函数,详细介绍能够看 阮一峰Thunk 函数的含义和用法,不管是Promise仍是thunk函数,其核心都是经过 传入回调的方式来实现Generator的自动执行。thunk函数只做为一个拓展知识,理解有困难的同窗也能够跳过这里,并不影响后续理解。
虽然咱们实现了Generator的自动执行以及让yield返回resolve的值,但上边的代码还存在着几点问题:
yield
后面跟Promise,为了兼容后面跟着基本类型值的状况,咱们须要把yield跟的内容(gen().next.value
)都用Promise.resolve()
转化一遍Generator.prototype.throw()
,把错误抛出来,才能被外层的try-catch捕获到async/await
的返回值是一个Promise,咱们这里也须要保持一致,给返回值包一个Promise咱们改造一下run方法:
function run(gen) { //把返回值包装成promise return new Promise((resolve, reject) => { var g = gen() function step(val) { //错误处理 try { var res = g.next(val) } catch(err) { return reject(err); } if(res.done) { return resolve(res.value); } //res.value包装为promise,以兼容yield后面跟基本类型的状况 Promise.resolve(res.value).then( val => { step(val); }, err => { //抛出错误 g.throw(err) }); } step(); }); } 复制代码
而后咱们能够测试一下:
function* myGenerator() { try { console.log(yield Promise.resolve(1)) console.log(yield 2) //2 console.log(yield Promise.reject('error')) } catch (error) { console.log(error) } } const result = run(myGenerator) //result是一个Promise //输出 1 2 error 复制代码
到这里,一个async/await
的实现基本完成了。最后咱们能够看一下babel对async/await的转换结果,其实总体的思路是同样的,可是写法稍有不一样:
//至关于咱们的run() function _asyncToGenerator(fn) { return function() { var self = this var args = arguments return new Promise(function(resolve, reject) { var gen = fn.apply(self, args); //至关于咱们的step() function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'next', value); } //处理异常 function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, 'throw', err); } _next(undefined); }); }; } function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } 复制代码
使用方式:
const foo = _asyncToGenerator(function* () { try { console.log(yield Promise.resolve(1)) //1 console.log(yield 2) //2 return '3' } catch (error) { console.log(error) } }) foo().then(res => { console.log(res) //3 }) 复制代码
有关async/await
的实现,到这里告一段落。可是直到结尾,咱们也不知道await究竟是如何暂停执行的,有关await暂停执行的秘密,咱们还要到Generator的实现中去寻找答案
咱们从一个简单的例子开始,一步步探究Generator的实现原理:
function* foo() { yield 'result1' yield 'result2' yield 'result3' } const gen = foo() console.log(gen.next().value) console.log(gen.next().value) console.log(gen.next().value) 复制代码
咱们能够在babel官网上在线转化这段代码,看看ES5环境下是如何实现Generator的:
"use strict"; var _marked = /*#__PURE__*/ regeneratorRuntime.mark(foo); function foo() { return regeneratorRuntime.wrap(function foo$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: _context.next = 2; return 'result1'; case 2: _context.next = 4; return 'result2'; case 4: _context.next = 6; return 'result3'; case 6: case "end": return _context.stop(); } } }, _marked); } var gen = foo(); console.log(gen.next().value); console.log(gen.next().value); console.log(gen.next().value); 复制代码
代码咋一看不长,但若是仔细观察会发现有两个不认识的东西 —— regeneratorRuntime.mark
和regeneratorRuntime.wrap
,这二者实际上是 regenerator-runtime 模块里的两个方法,regenerator-runtime 模块来自facebook的 regenerator 模块,完整代码在runtime.js,这个runtime有700多行...-_-||,所以咱们不能全讲,不过重要的部分咱们就简单地过一下,重点讲解暂停执行相关部分代码
我的以为啃源码的效果不是很好,建议读者拉到末尾先看结论和简略版实现,源码做为一个补充理解
regeneratorRuntime.mark(foo)
这个方法在第一行被调用,咱们先看一下runtime里mark()方法的定义
//runtime.js里的定义稍有不一样,多了一些判断,如下是编译后的代码 runtime.mark = function(genFun) { genFun.__proto__ = GeneratorFunctionPrototype; genFun.prototype = Object.create(Gp); return genFun; }; 复制代码
这里边GeneratorFunctionPrototype
和Gp
咱们都不认识,他们被定义在runtime里,不过不要紧,咱们只要知道mark()方法
为生成器函数(foo)绑定了一系列原型就能够了,这里就简单地过了
从上面babel转化的代码咱们能看到,执行foo()
,其实就是执行wrap()
,那么这个方法起到什么做用呢,他想包装一个什么东西呢,咱们先来看看wrap方法的定义:
//runtime.js里的定义稍有不一样,多了一些判断,如下是编译后的代码 function wrap(innerFn, outerFn, self) { var generator = Object.create(outerFn.prototype); var context = new Context([]); generator._invoke = makeInvokeMethod(innerFn, self, context); return generator; } 复制代码
wrap方法先是建立了一个generator,并继承outerFn.prototype
;而后new了一个context对象
;makeInvokeMethod方法
接收innerFn(对应foo$)
、context
和this
,并把返回值挂到generator._invoke
上;最后return了generator。其实wrap()至关因而给generator增长了一个_invoke方法
这段代码确定让人产生不少疑问,outerFn.prototype是什么,Context又是什么,makeInvokeMethod又作了哪些操做。下面咱们就来一一解答:
outerFn.prototype
其实就是genFun.prototype
,
这个咱们结合一下上面的代码就能知道
context
能够直接理解为这样一个全局对象,用于储存各类状态和上下文:
var ContinueSentinel = {}; var context = { done: false, method: "next", next: 0, prev: 0, abrupt: function(type, arg) { var record = {}; record.type = type; record.arg = arg; return this.complete(record); }, complete: function(record, afterLoc) { if (record.type === "return") { this.rval = this.arg = record.arg; this.method = "return"; this.next = "end"; } return ContinueSentinel; }, stop: function() { this.done = true; return this.rval; } }; 复制代码
makeInvokeMethod
的定义以下,它return了一个invoke方法
,invoke用于判断当前状态和执行下一步,其实就是咱们调用的next()
//如下是编译后的代码 function makeInvokeMethod(innerFn, context) { // 将状态置为start var state = "start"; return function invoke(method, arg) { // 已完成 if (state === "completed") { return { value: undefined, done: true }; } context.method = method; context.arg = arg; // 执行中 while (true) { state = "executing"; var record = { type: "normal", arg: innerFn.call(self, context) // 执行下一步,并获取状态(其实就是switch里边return的值) }; if (record.type === "normal") { // 判断是否已经执行完成 state = context.done ? "completed" : "yield"; // ContinueSentinel实际上是一个空对象,record.arg === {}则跳过return进入下一个循环 // 何时record.arg会为空对象呢, 答案是没有后续yield语句或已经return的时候,也就是switch返回了空值的状况(跟着上面的switch走一下就知道了) if (record.arg === ContinueSentinel) { continue; } // next()的返回值 return { value: record.arg, done: context.done }; } } }; } 复制代码
为何generator._invoke
实际上就是gen.next
呢,由于在runtime对于next()的定义中,next()其实就return了_invoke方法
// Helper for defining the .next, .throw, and .return methods of the // Iterator interface in terms of a single ._invoke method. function defineIteratorMethods(prototype) { ["next", "throw", "return"].forEach(function(method) { prototype[method] = function(arg) { return this._invoke(method, arg); }; }); } defineIteratorMethods(Gp); 复制代码
这么一遍源码下来,估计不少读者仍是懵逼的,毕竟源码中纠集了不少概念和封装,一时半会很差彻底理解,让咱们跳出源码,实现一个简单的Generator,而后再回过头看源码,会获得更清晰的认识
// 生成器函数根据yield语句将代码分割为switch-case块,后续经过切换_context.prev和_context.next来分别执行各个case function gen$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: _context.next = 2; return 'result1'; case 2: _context.next = 4; return 'result2'; case 4: _context.next = 6; return 'result3'; case 6: case "end": return _context.stop(); } } } // 低配版context var context = { next:0, prev: 0, done: false, stop: function stop () { this.done = true } } // 低配版invoke let gen = function() { return { next: function() { value = context.done ? undefined: gen$(context) done = context.done return { value, done } } } } // 测试使用 var g = gen() g.next() // {value: "result1", done: false} g.next() // {value: "result2", done: false} g.next() // {value: "result3", done: false} g.next() // {value: undefined, done: true} 复制代码
这段代码并不难理解,咱们分析一下调用流程:
function*
生成器函数被转化为以上代码转化后的代码分为三大块:
gen$(_context)
由yield分割生成器函数代码而来context对象
用于储存函数执行上下文invoke()方法
定义next(),用于执行gen$(_context)来跳到下一步g.next()
,就至关于调用invoke()方法
,执行gen$(_context)
,进入switch语句,switch根据context的标识,执行对应的case块,return对应结果g.next()
返回{value: undefined, done: true}
从中咱们能够看出,Generator实现的核心在于上下文的保存
,函数并无真的被挂起,每一次yield,其实都执行了一遍传入的生成器函数,只是在这个过程当中间用了一个context对象储存上下文,使得每次执行生成器函数的时候,均可以从上一个执行结果开始执行,看起来就像函数被挂起了同样