原文: Write Your Own Node.js Promise Library from Scratch做者:code_barbarianhtml
Promise 已是 JavaScript 中异步处理的基石,回调的场景将会愈来愈少,并且如今能够直接在 Node.js 使用 async/await。async/await 基于 Promise,所以须要了解 Promise 来掌握 async/await。这篇文章,将介绍如何编写一个 Promise 库,并演示如何使用 async/await。node
在 ES6 规范中,Promise 是一个类,它的构造函数接受一个 executor
函数。Promise 类的实例有一个 then()
方法。根据规范,Promise 还有其余的一些属性,但在这里能够暂时忽略,由于咱们要实现的是一个精简版的库。下面是一个 MyPromise
类的脚手架:bootstrap
class MyPromise { // `executor` 函数接受两个参数,`resolve()` 和 `reject()` // 负责在异步操做成功(resolved)或者失败(rejected)的时候调用 `resolve()` 或者 `reject()` constructor(executor) {} // 当 promise 的状态是 fulfilled(完成)时调用 `onFulfilled` 方法, // 当 promise 的状态是 rejected(失败)时调用 `onRejected` 方法 // 到目前为止,能够认为 'fulfilled' 和 'resolved' 是同样的 then(onFulfilled, onRejected) {} }
executor
函数须要两个参数,resolve()
和 reject()
。promise 是一个状态机,包含三个状态:数组
这样很容易就能实现 MyPromise
构造函数的初始版本:promise
constructor(executor) { if (typeof executor !== 'function') { throw new Error('Executor must be a function') } // 初始状态,$state 表示 promise 的当前状态 // $chained 是当 promise 处在 settled 状态时须要调用的函数数组 this.$state = 'PENDING' this.$chained = [] // 为处理器函数实现 `resolve()` 和 `reject()` const resolve = res => { // 只要当 `resolve()` 或 `reject()` 被调用 // 这个 promise 对象就再也不处于 pending 状态,被称为 settled 状态 // 调用 `resolve()` 或 `reject()` 两次,以及在 `resolve()` 以后调用 `reject()` 是无效的 if (this.$state !== 'PENDING') { return } // 后面将会谈到 fulfilled 和 resolved 之间存在细微差异 this.$state = 'FULFILLED' this.$internalValue = res // If somebody called `.then()` while this promise was pending, need // to call their `onFulfilled()` function for (const { onFulfilled } of this.$chained) { onFulfilled(res) } } const reject = err => { if (this.$state !== 'PENDING') { return } this.$state = 'REJECTED' this.$internalValue = err for (const { onRejected } of this.$chained) { onRejected(err) } } // 如规范所言,调用处理器函数中的 `resolve()` 和 `reject()` try { // 若是处理器函数抛出一个同步错误,咱们认为这是一个失败状态 // 须要注意的是,`resolve()` 和 `reject()` 只能被调用一次 executor(resolve, reject) } catch (err) { reject(err) } }
then()
函数的实现更简单,它接受两个参数,onFulfilled()
和 onRejected()
。then()
函数必须确保 promise 在 fulfilled 时调用 onFulfilled()
,在 rejected 时调用 onRejected()
。若是 promise 已经 resolved 或 rejected,then()
函数会当即调用 onFulfilled()
或 onRejected()
。若是 promise 仍处于 pending 状态,就将函数推入 $chained
数组,所以后续 resolve()
和 reject()
函数仍然能够调用它们。异步
then(onFulfilled, onRejected) { if (this.$state === 'FULFILLED') { onFulfilled(this.$internalValue) } else if (this.$state === 'REJECTED') { onRejected(this.$internalValue) } else { this.$chained.push({ onFulfilled, onRejected }) } }
*除此以外:ES6 规范表示,若是在已经 resolved 或 rejected 的 promise 调用 .then()
, 那么 onFulfilled()
或 onRejected()
将在下一个时序被调用。因为本文代码只是一个教学示例而不是规范的精确实现,所以实现会忽略这些细节。async
上面的例子特地忽略了 promise 中最复杂也是最有用的部分:链式调用。若是 onFulfilled()
或者 onRejected()
函数返回一个 promise,则 then()
应该返回一个 “locked in” 的新 promise 以匹配这个 promise 的状态。例如:ide
p = new MyPromise(resolve => { setTimeout(() => resolve('World'), 100) }) p .then(res => new MyPromise(resolve => resolve(`Hello, ${res}`))) // 在 100 ms 后打印 'Hello, World' .then(res => console.log(res))
下面是能够返回 promise 的 .then()
函数实现,这样就能够进行链式调用。函数
then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => { // 确保在 `onFulfilled()` 和 `onRejected()` 的错误将致使返回的 promise 失败(reject) const _onFulfilled = res => { try { // 若是 `onFulfilled()` 返回一个 promise, 确保 `resolve()` 能正确处理 resolve(onFulfilled(res)) } catch (err) { reject(err) } } const _onRejected = err => { try { reject(onRejected(err)) } catch (_err) { reject(_err) } } if (this.$state === 'FULFILLED') { _onFulfilled(this.$internalValue) } else if (this.$state === 'REJECTED') { _onRejected(this.$internalValue) } else { this.$chained.push({ onFulfilled: _onFulfilled, onRejected: _onRejected }) } }) }
如今 then()
返回一个 promise,可是还须要完成一些工做:若是 onFulfilled()
返回一个 promise,resolve()
要可以正确处理。因此 resolve()
函数须要在 then()
递归调用,下面是更新后的 resolve()
函数:ui
const resolve = res => { // 只要当 `resolve()` 或 `reject()` 被调用 // 这个 promise 对象就再也不处于 pending 状态,被称为 settled 状态 // 调用 `resolve()` 或 `reject()` 两次,以及在 `resolve()` 以后调用 `reject()` 是无效的 if (this.$state !== 'PENDING') { return } // 若是 `res` 是 thenable(带有then方法的对象) // 将锁定 promise 来保持跟 thenable 的状态一致 if (res !== null && typeof res.then === 'function') { // 在这种状况下,这个 promise 是 resolved,可是仍处于 'PENDING' 状态 // 这就是 ES6 规范中说的"一个 resolved 的 promise",可能处在 pending, fulfilled 或者 rejected 状态 // http://www.ecma-international.org/ecma-262/6.0/#sec-promise-objects return res.then(resolve, reject) } this.$state = 'FULFILLED' this.$internalValue = res // If somebody called `.then()` while this promise was pending, need // to call their `onFulfilled()` function for (const { onFulfilled } of this.$chained) { onFulfilled(res) } return res }
为了简单起见,上面的例子省略了一旦 promise 被锁定用以匹配另外一个 promise 时,调用 resolve() 或者 reject() 是无效的关键细节。在上面的例子中,你能够 resolve() 一个 pending 的 promise ,而后抛出一个错误,而后 res.then(resolve, reject) 将会无效。这仅仅是一个例子,而不是 ES6 promise 规范的彻底实现。
上面的代码说明了 resolved 的 promise 和 fulfilled 的 promise 之间的区别。这种区别是微妙的,而且与 promise 链式调用有关。resolved 不是一种真正的 promise 状态,但它是ES6规范中定义的术语。当对一个已经 resolved 的 promise 调用 resolve()
,可能会发生如下两件事之一:
resolve(v)
时,若是 v
不是一个 promise ,那么 promise 当即成为 fulfilled。在这种简单的状况下,resolved 和 fulfilled 就是同样的。resolve(v)
时,若是 v
是另外一个 promise,那么这个 promise 一直处于 pending 直到 v
调用 resolve 或者 reject。在这种状况下, promise 是 resolved 但处于 pending 状态。关键字 await
会暂停执行一个 async
函数,直到等待的 promise 变成 settled 状态。如今咱们已经有了一个简单的自制 promise 库,看看结合使用 async/await 中时会发生什么。向 then()
函数添加一个 console.log()
语句:
then(onFulfilled, onRejected) { console.log('Then', onFulfilled, onRejected, new Error().stack) return new MyPromise((resolve, reject) => { /* ... */ }) }
如今,咱们来 await
一个 MyPromise
的实例,看看会发生什么。
run().catch(error => console.error(error.stack)) async function run() { const start = Date.now() await new MyPromise(resolve => setTimeout(() => resolve(), 100)) console.log('Elapsed time', Date.now() - start) }
注意上面的 .catch()
调用。catch()
函数是 ES6 promise 规范的核心部分。本文不会详细讲述它,由于 .catch(f)
至关于 .then(null, f)
,没有什么特别的内容。
如下是输出内容,注意 await 隐式调用 .then()
中的 onFulfilled()
和 onRejected()
函数,这是 V8 底层的 C++ 代码(native code)。此外,await
会一直等待调用 .then()
直到下一个时序。
Then function () { [native code] } function () { [native code] } Error at MyPromise.then (/home/val/test/promise.js:63:50) at process._tickCallback (internal/process/next_tick.js:188:7) at Function.Module.runMain (module.js:686:11) at startup (bootstrap_node.js:187:16) at bootstrap_node.js:608:3 Elapsed time 102
async/await 是很是强大的特性,但掌握起来稍微有点困难,由于须要使用者了解 promise 的基本原则。 promise 有不少细节,例如捕获处理器函数中的同步错误,以及 promise 一旦解决就没法改变状态,这使得 async/await 成为可能。一旦对 promise 有了充分的理解,async/await 就会变得容易得多。