if (typeof Promise === 'undefined') { return }
实现 Promise/A+ 规范的库有不少,lie 是一个精简的实现 Promise/A+ 的库,而且经过了 Promise/A+ 专门的测试集,但 lie 的代码写的有点绕,我在 lie 的代码基础上进行了修改,使之更容易阅读和理解,并发布了 appoint 模块供你们参考。
Promise/A+ 规范node
Promise 规范有不少,如 Promise/A,Promise/B,Promise/D 以及 Promise/A 的升级版 Promise/A+,有兴趣的能够去了解下,最终 ES6 中采用了 Promise/A+ 规范。在讲解 Promise 实现以前,固然要先了解 Promise/A+ 规范。Promise/A+ 规范参考:git
英文版:https://promisesaplus.com/
中文版:http://malcolmyu.github.io/ma...
注意:没有特殊说明如下 promise 均指代 Promise 实例。
规范虽然不长,但细节也比较多,我挑出几个要点简单说明下:
Promise 本质是一个状态机。每一个 promise 只能是 3 种状态中的一种:pending、fulfilled 或 rejected。状态转变只能是 pending -> fulfilled 或者 pending -> rejected。状态转变不可逆。
then 方法能够被同一个 promise 调用屡次。
then 方法必须返回一个 promise。规范里没有明确说明返回一个新的 promise 仍是复用老的 promise(即 return this),大多数实现都是返回一个新的 promise,并且复用老的 promise 可能改变内部状态,这与规范也是相违背的。
值穿透。下面会细讲。
从头实现 Promisegithub
咱们知道 Promise 是一个构造函数,须要用 new 调用,并有如下几个 api:api
function Promise(resolver) {} Promise.prototype.then = function() {} Promise.prototype.catch = function() {} Promise.resolve = function() {} Promise.reject = function() {} Promise.all = function() {} Promise.race = function() {}
下面咱们以 appoint 为最终目标,开始一步一步构建完整的 Promise 实现。数组
'use strict'; var immediate = require('immediate'); function INTERNAL() {} function isFunction(func) { return typeof func === 'function'; } function isObject(obj) { return typeof obj === 'object'; } function isArray(arr) { return Object.prototype.toString.call(arr) === '[object Array]'; } var PENDING = 0; var FULFILLED = 1; var REJECTED = 2; module.exports = Promise; function Promise(resolver) { if (!isFunction(resolver)) { throw new TypeError('resolver must be a function'); } this.state = PENDING; this.value = void 0; this.queue = []; if (resolver !== INTERNAL) { safelyResolveThen(this, resolver); } }
immediate 是一个将同步转异步执行的库。INTERNAL 就是一个空函数,相似于一些代码库中的 noop。定义了 3 个辅助函数:isFunction、isObject 和 isArray。定义了 3 种状态:PENDING、FULFILLED 和 REJECTED。safelyResolveThen 后面讲。promise 内部有三个变量:
state: 当前 promise 的状态,初始值为 PENDING。状态改变只能是 PENDING -> FULFILLED 或 PENDING -> REJECTED。
value: 当 state 是 FULFILLED 时存储返回值,当 state 是 REJECTED 时存储错误。
queue: promise 内部的回调队列,这是个什么玩意儿?为何是一个数组?
Promise 实现基本原理promise
先看一段代码:安全
var Promise = require('appoint') var promise = new Promise((resolve) => { setTimeout(() => { resolve('haha') }, 1000) }) var a = promise.then(function onSuccess() {}) var b = promise.catch(function onError() {}) console.log(require('util').inspect(promise, { depth: 10 })) console.log(promise.queue[0].promise === a) console.log(promise.queue[1].promise === b)
打印出:并发
Promise { state: 0, value: undefined, queue: [ QueueItem { promise: Promise { state: 0, value: undefined, queue: [] }, callFulfilled: [Function], callRejected: [Function] }, QueueItem { promise: Promise { state: 0, value: undefined, queue: [] }, callFulfilled: [Function], callRejected: [Function] } ] } true true
能够看出,queue 数组中有两个对象。由于规范中规定:then 方法能够被同一个 promise 调用屡次。上例中在调用 .then 和 .catch 时 promise 并无被 resolve,因此将 .then 和 .catch 生成的新 promise(a 和 b) 和正确时的回调(onSuccess 包装成 callFulfilled)和错误时的回调(onError 包装成 callRejected)生成一个 QueueItem 实例并 push 到 queue 数组里,因此上面两个 console.log 打印 true。当 promise 状态改变时遍历内部 queue 数组,统一执行成功(FULFILLED -> callFulfilled)或失败(REJECTED -> callRejected)的回调(传入 promise 的 value 值),生成的结果分别设置 a 和 b 的 state 和 value,这就是 Promise 实现的基本原理。
再来看另外一个例子:app
var Promise = require('appoint') var promise = new Promise((resolve) => { setTimeout(() => { resolve('haha') }, 1000) }) promise .then(() => {}) .then(() => {}) .then(() => {}) console.log(require('util').inspect(promise, { depth: 10 }))
打印出:异步
Promise { state: 0, value: undefined, queue: [ QueueItem { promise: Promise { state: 0, value: undefined, queue: [ QueueItem { promise: Promise { state: 0, value: undefined, queue: [ QueueItem { promise: Promise { state: 0, value: undefined, queue: [] }, callFulfilled: [Function], callRejected: [Function] } ] }, callFulfilled: [Function], callRejected: [Function] } ] }, callFulfilled: [Function], callRejected: [Function] } ] }
调用了 3 次 then,每一个 then 将它生成的 promise 放到了调用它的 promise 队列里,造成了 3 层调用关系。当最外层的 promise 状态改变时,遍历它的 queue 数组调用对应的回调,设置子 promise 的 state 和 value 并遍历它的 queue 数组调用对应的回调,而后设置孙 promise 的 state 和 value 并遍历它的 queue 数组调用对应的回调......依次类推。
safelyResolveThen
function safelyResolveThen(self, then) { var called = false; try { then(function (value) { if (called) { return; } called = true; doResolve(self, value); }, function (error) { if (called) { return; } called = true; doReject(self, error); }); } catch (error) { if (called) { return; } called = true; doReject(self, error); } }
safelyResolveThen 顾名思义用来『安全的执行 then 函数』,这里的 then 函数指『第一个参数是 resolve 函数第二个参数是 reject 函数的函数』,以下两种状况:
一、构造函数的参数,即这里的 resolver:
new Promise(function resolver(resolve, reject) { setTimeout(() => { resolve('haha') }, 1000) })
二、promise 的 then:
promise.then(resolve, reject)
safelyResolveThen 有 3 个做用:
一、try...catch 捕获抛出的异常,如:
new Promise(function resolver(resolve, reject) {
throw new Error('Oops')
})
二、called 控制 resolve 或 reject 只执行一次,屡次调用没有任何做用。即:
var Promise = require('appoint') var promise = new Promise(function resolver(resolve, reject) { setTimeout(() => { resolve('haha') }, 1000) reject('error') }) promise.then(console.log) promise.catch(console.error)
打印 error,不会再打印 haha。
三、没有错误则执行 doResolve,有错误则执行 doReject。
doResolve 和 doReject
function doResolve(self, value) { try { var then = getThen(value); if (then) { safelyResolveThen(self, then); } else { self.state = FULFILLED; self.value = value; self.queue.forEach(function (queueItem) { queueItem.callFulfilled(value); }); } return self; } catch (error) { return doReject(self, error); } } function doReject(self, error) { self.state = REJECTED; self.value = error; self.queue.forEach(function (queueItem) { queueItem.callRejected(error); }); return self; }
doReject 就是设置 promise 的 state 为 REJECTED,value 为 error,callRejected 如前面提到的通知子 promise:『我这里出了点问题呀』而后子 promise 根据传入的错误设置本身的状态和值。doResolve 结合 safelyResolveThen 使用不断地解包 promise,直至返回值是非 promise 对象后,设置 promise 的状态和值,而后通知子 promise:『我这里有值了哟』而后子 promise 根据传入的值设置本身的状态和值。
这里有个辅助函数 getThen:
function getThen(obj) { var then = obj && obj.then; if (obj && (isObject(obj) || isFunction(obj)) && isFunction(then)) { return function appyThen() { then.apply(obj, arguments); }; } }
规范中规定:若是 then 是函数,将 x(这里是 obj) 做为函数的 this 调用。
Promise.prototype.then 和 Promise.prototype.catch
Promise.prototype.then = function (onFulfilled, onRejected) { if (!isFunction(onFulfilled) && this.state === FULFILLED || !isFunction(onRejected) && this.state === REJECTED) { return this; } var promise = new this.constructor(INTERNAL); if (this.state !== PENDING) { var resolver = this.state === FULFILLED ? onFulfilled : onRejected; unwrap(promise, resolver, this.value); } else { this.queue.push(new QueueItem(promise, onFulfilled, onRejected)); } return promise; }; Promise.prototype.catch = function (onRejected) { return this.then(null, onRejected); };
上述代码中的 return this 实现了值穿透,后面会讲。能够看出,then 方法中生成了一个新的 promise 而后返回,符合规范要求。若是 promise 的状态改变了,则调用 unwrap,不然将生成的 promise 加入到当前 promise 的回调队列 queue 里,以前讲解了如何消费 queue。有 3 点须要讲解:
一、Promise 构造函数传入了一个 INTERNAL 即空函数,由于这个新产生的 promise 能够认为是内部的 promise,须要根据外部的 promise 的状态和值产生自身的状态和值,不须要传入回调函数,而外部 Promise 须要传入回调函数决定它的状态和值。因此以前 Promise 的构造函数里作了判断区分外部调用仍是内部调用:
if (resolver !== INTERNAL) { safelyResolveThen(this, resolver); }
二、unwrap 代码以下:
function unwrap(promise, func, value) { immediate(function () { var returnValue; try { returnValue = func(value); } catch (error) { return doReject(promise, error); } if (returnValue === promise) { doReject(promise, new TypeError('Cannot resolve promise with itself')); } else { doResolve(promise, returnValue); } }); }
从名字也能够理解是用来解包(即执行函数)的,第一个参数是子 promise,第二个参数是父 promise 的 then 的回调(onFulfilled/onRejected),第三个参数是父 promise 的值(正常值/错误)。有 3 点须要说明:
一、使用 immediate 将同步代码变异步。如:
var Promise = require('appoint') var promise = new Promise((resolve, reject) => { setTimeout(() => { resolve('haha') }, 1000) }) promise.then(() => { promise.then(() => { console.log('1') }) console.log('2') })
打印 2 1,去掉 immediate 则打印 1 2。
二、try...catch 用来捕获 then/catch 内抛出的异常,并调用 doReject,如:
promise.then(() => { throw new Error('haha') }) promise.catch(() => { throw new Error('haha') })
三、返回的值不能是 promise 自己,不然会形成死循环,如 node@4.6.0 下运行:
var promise = new Promise((resolve, reject) => { setTimeout(() => { resolve('haha') }, 1000) }) var a = promise.then(() => { return a }) a.catch(console.log)// [TypeError: Chaining cycle detected for promise #<Promise>]
QueueItem 代码以下:
function QueueItem(promise, onFulfilled, onRejected) { this.promise = promise; this.callFulfilled = function (value) { doResolve(this.promise, value); }; this.callRejected = function (error) { doReject(this.promise, error); }; if (isFunction(onFulfilled)) { this.callFulfilled = function (value) { unwrap(this.promise, onFulfilled, value); }; } if (isFunction(onRejected)) { this.callRejected = function (error) { unwrap(this.promise, onRejected, error); }; } } promi
se 为 then 生成的新 promise(如下称为『子promise』),onFulfilled 和 onRejected 便是 then 参数中的 onFulfilled 和 onRejected。从上面代码能够看出:好比当 promise 状态变为 FULFILLED 时,以前注册的 then 函数,用 callFulfilled 调用 unwrap 进行解包最终得出子 promise 的状态和值,以前注册的 catch 函数,用 callFulfilled 直接调用 doResolve,设置队列里子 promise 的状态和值。当 promise 状态变为 REJECTED 相似。
注意:promise.catch(onRejected) 就是 promise.then(null, onRejected) 的语法糖。
至此,Promise 的核心实现都完成了。
值穿透
Promise.prototype.then = function (onFulfilled, onRejected) { if (!isFunction(onFulfilled) && this.state === FULFILLED || !isFunction(onRejected) && this.state === REJECTED) { return this; } ... };
上面提到了值穿透问题,值穿透即:
var Promise = require('appoint') var promise = new Promise((resolve, reject) => { setTimeout(() => { resolve('haha') }, 1000) }) promise .then('hehe') .then(console.log)
最终打印 haha 而不是 hehe。
经过 return this 只实现了值穿透的一种状况,其实值穿透有两种状况:
一、promise 已是 FULFILLED/REJECTED 时,经过 return this 实现的值穿透:
var Promise = require('appoint') var promise = new Promise(function (resolve) { setTimeout(() => { resolve('haha') }, 1000) }) promise.then(() => { promise.then().then((res) => {// ① console.log(res)// haha }) promise.catch().then((res) => {// ② console.log(res)// haha }) console.log(promise.then() === promise.catch())// true console.log(promise.then(1) === promise.catch({ name: 'nswbmw' }))// true })
上述代码①②处 promise 已是 FULFILLED 了符合条件因此执行了 return this。注意:原生的 Promise 实现里并非这样实现的,因此会打印两个 false。
二、promise 是 PENDING 时,经过生成新的 promise 加入到父 promise 的 queue,父 promise 有值时调用 callFulfilled->doResolve 或 callRejected->doReject(由于 then/catch 传入的参数不是函数)设置子 promise 的状态和值为父 promise 的状态和值。如:
var Promise = require('appoint') var promise = new Promise((resolve) => { setTimeout(() => { resolve('haha') }, 1000) }) var a = promise.then() a.then((res) => { console.log(res)// haha }) var b = promise.catch() b.then((res) => { console.log(res)// haha }) console.log(a === b)// false
Promise.resolve 和 Promise.reject
Promise.resolve = resolve; function resolve(value) { if (value instanceof this) { return value; } return doResolve(new this(INTERNAL), value); } Promise.reject = reject; function reject(reason) { var promise = new this(INTERNAL); return doReject(promise, reason); }
当 Promise.resolve 参数是一个 promise 时,直接返回该值。
Promise.all Promise.all = all; function all(iterable) { var self = this; if (!isArray(iterable)) { return this.reject(new TypeError('must be an array')); } var len = iterable.length; var called = false; if (!len) { return this.resolve([]); } var values = new Array(len); var resolved = 0; var i = -1; var promise = new this(INTERNAL); while (++i < len) { allResolver(iterable[i], i); } return promise; function allResolver(value, i) { self.resolve(value).then(resolveFromAll, function (error) { if (!called) { called = true; doReject(promise, error); } }); function resolveFromAll(outValue) { values[i] = outValue; if (++resolved === len && !called) { called = true; doResolve(promise, values); } } } }
Promise.all 用来并行执行多个 promise/值,当全部 promise/值执行完毕后或有一个发生错误时返回。能够看出:
一、Promise.all 内部生成了一个新的 promise 返回。
二、called 用来控制即便有多个 promise reject 也只有第一个生效。
三、values 用来存储结果。
四、当最后一个 promise 得出结果后,使用 doResolve(promise, values) 设置 promise 的 state 为 FULFILLED,value 为结果数组 values。
Promise.race
Promise.race = race; function race(iterable) { var self = this; if (!isArray(iterable)) { return this.reject(new TypeError('must be an array')); } var len = iterable.length; var called = false; if (!len) { return this.resolve([]); } var i = -1; var promise = new this(INTERNAL); while (++i < len) { resolver(iterable[i]); } return promise; function resolver(value) { self.resolve(value).then(function (response) { if (!called) { called = true; doResolve(promise, response); } }, function (error) { if (!called) { called = true; doReject(promise, error); } }); } }
Promise.race 接受一个数组,当数组中有一个 resolve 或 reject 时返回。跟 Promise.all 代码相近,只不过这里用 called 控制只要有任何一个 promise onFulfilled/onRejected 当即去设置 promise 的状态和值。至此,Promise 的实现所有讲解完毕。