手写一个promise
[toc]
本文遵循的Promise/A+
规范实现一个简略版本Promise, 用代码理解规范中的每一句话.
Promise/A+
英文版规范, 中文版规范;- es6中Promise完整规范
- 浏览器中Promise实现
Promise 的状态
规范描述
一个 Promise 的当前状态必须为以下三种状态中的一种:等待态(Pending)、完成态(Fulfilled)和拒绝态(Rejected)。
- 等待态(Pending)
处于等待态时,promise 需满足以下条件:- 可以迁移至执行态或拒绝态
- 完成态(Fulfilled)
处于执行态时,promise 需满足以下条件:- 不能迁移至其他任何状态
- 必须拥有一个不可变的终值
- 拒绝态(Rejected)
处于拒绝态时,promise 需满足以下条件:- 不能迁移至其他任何状态
- 必须拥有一个不可变的据因
代码实现
采用类实现:
class MyPromise {
}
实现构造函数:
const _ = require('lodash');
const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2;
class MyPromise {
constructor(executor) {
if (!(this instanceof MyPromise)) {
throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
}
if (!_.isFunction(executor)) {
throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
}
this._status = PENDING;
this._value = undefined;
this._thenCallbacks = [];
this._reason = undefined;
try {
executor((...args) => this._resolve(...args), (...args) => this._reject(...args));
} catch(e) {
this._reject(e);
}
}
}
在构造函数中, 首先处理函数边界, 然后初始化状态,终值(_value), 拒因(_reason)和then
的回调函数队列; 最后调用传入的executor
.executor
中用于控制Promise
状态的变化.传递两个回调函数作为参数,第一个参数叫做resove
,第二个参数叫做 reject
, 在对象中的处理处理逻辑为:
_resolve(value) {
if (this._status !== PENDING) {
return;
}
this._status = FULFILLED;
this._value = value;
}
_reject(reason) {
if (this._status !== PENDING) {
return;
}
this._status = REJECTED;
this._reason = reason;
}
_resolve
, _reject
有状态边界判断, 如果被调用多次, 采用首次调用并忽略剩下的调用._resolve
中只允许等待态转为完成态,然后接收终值._reject
中只允许等待态转为拒绝态, 然后接收拒因.
状态的判断保证
_resolve
,_reject
中主要逻辑在当前promise
中最多被执行一次: 状态最多改变一次;then
的回调函数最多调用一次.
Then函数
规范描述
一个promise
必须提供一个then
方法以访问其当前值、终值和据因。
promise
的then
方法接受两个参数:
promise.then(onFulfilled, onRejected)
参数可选
onFulfilled
和onRejected
都是可选参数。
- 如果
onFulfilled
不是函数,其必须被忽略 - 如果
onRejected
不是函数,其必须被忽略
onFulfilled
特性
如果onFulfilled
是函数:
- 当
promise
执行结束后其必须被调用,其第一个参数为promise
的终值 - 在
promise
执行结束前其不可被调用 - 其调用次数不可超过一次
调用时机
onFulfilled
和onRejected
只有在执行环境堆栈仅包含平台代码时才可被调用
调用要求
onFulfilled
和onRejected
必须被作为函数调用(即没有 this 值)注2
多次调用
then
方法可以被同一个promise
调用多次
当promise
成功执行时,所有onFulfilled
需按照其注册顺序依次回调
当promise
被拒绝执行时,所有的onRejected
需按照其注册顺序依次回调
返回
then
方法必须返回一个promise
对象
promise2 = promise1.then(onFulfilled, onRejected);
- 如果
onFulfilled
或者onRejected
返回一个值 x ,则运行下面的Promise
解决过程:[[Resolve]](promise2, x)
- 如果
onFulfilled
或者onRejected
抛出一个异常e
,则promise2
必须拒绝执行,并返回拒因e
- 如果
onFulfilled
不是函数且promise1
成功执行,promise2
必须成功执行并返回相同的值 - 如果 onRejected 不是函数且
promise1
拒绝执行,promise2
必须拒绝执行并返回相同的据因
代码实现
需要在构造函数添加回调函数队列:
constructor(executor) {
// ...
this._value = undefined;
this._thenCallbacks = [];
this._reason = undefined;
// ...
}
首先, 实现一个then
函数.由于本章节代码牵扯到很多部分, 所以尽量用代码注释来说明实现的规范:
then(onFulfilled, onRejected) {
// 如果 onFulfilled 不是函数,其必须被忽略
const _onFulfilled = _.isFunction(onFulfilled) ? onFulfilled : void 0;
// 如果 onRejected 不是函数,其必须被忽略
const _onRejected = _.isFunction(onRejected) ? onRejected : void 0;
// then 方法可以被同一个 promise 调用多次
this._thenCallbacks.push([_onFulfilled, _onRejected]);
return new MyPromise((resolve, reject) => {
// 等待实现
});
}
onFulfilled
和onRejected
, 需要在_resove
或者reject
时被调用, 将resolve
, reject
改造为:
_resolve(value) {
if (this._status !== PENDING) {
return;
}
this._status = FULFILLED;
this._value = value;
// 如果then的回调函数onFulfilled, onRejected为函数的话, 需要
// 在 promise 执行结束前其不可被调用,当 promise 执行结束后其必须被调用
// 其调用次数不可超过一次
// 只有在执行环境堆栈仅包含平台代码时才可被调用
process.nextTick(this._callThenCallbacks);
}
_reject(reason) {
if (this._status !== PENDING) {
return;
}
this._status = REJECTED;
this._reason = reason;
// 如果then的回调函数onFulfilled, onRejected为函数的话, 需要
// 在 promise 执行结束前其不可被调用,当 promise 执行结束后其必须被调用
// 其调用次数不可超过一次
// 只有在执行环境堆栈仅包含平台代码时才可被调用
process.nextTick(this._callThenCallbacks);
}
虽然规范中没有说明onFulfilled
和onRejected
必需为微任务(micro-task)还是宏任务(macro-task),但是其他实现基本都是基于微任务.
这里由于本文只是在node环境实现,所以采用process.nextTick
来启用微任务, 为了跨平台, 一般的Promise实现框架, 都会使用多种方式来实现在执行环境堆栈仅包含平台代码时才可被调用, 如MutationObserver
, MessageChannel
, vertx
等, 最后可能使用setTimeout
实现.
按照Promise/A+
规范来说,onFulfilled
和onRejected
只有在执行环境堆栈仅包含平台代码时才可被调用, 但是在nodejs
或者es6环境中的Promise
对象, 需要像下面的实现:
_resolve(value) {
// 只有在执行环境堆栈仅包含平台代码时才可被调用
process.nextTick(() => {
if (this._status !== PENDING) {
return;
}
this._status = FULFILLED;
this._value = value;
// 如果then的回调函数onFulfilled, onRejected为函数的话, 需要
// 在 promise 执行结束前其不可被调用,当 promise 执行结束后其必须被调用
// 其调用次数不可超过一次
// 只有在执行环境堆栈仅包含平台代码时才可被调用
// process.nextTick(() => this._callThenCallbacks());
this._callThenCallbacks();
});
}
_reject(reason) {
// 只有在执行环境堆栈仅包含平台代码时才可被调用
process.nextTick(() => {
if (this._status !== PENDING) {
return;
}
this._status = REJECTED;
this._reason = reason;
// 如果then的回调函数onFulfilled, onRejected为函数的话, 需要
// 在 promise 执行结束前其不可被调用,当 promise 执行结束后其必须被调用
// 其调用次数不可超过一次
// process.nextTick(() => this._callThenCallbacks());
this._callThenCallbacks();
});
}
对于比较流行的prmise polyfill库
es-promise
的实现,采用的是上面一种启用微任务的时机, 对于babel中的垫片core-js中实现的promise
, 采用的是下一种启用时机.
Then函数.返回规范有复杂的要求,为了实现这些要求, 需要改变上面的then
函数的实现:
then(onFulfilled, onRejected) {
// 如果 onFulfilled 不是函数,其必须被忽略
const _onFulfilled = _.isFunction(onFulfilled) ? onFulfilled : void 0;
// 如果 onRejected 不是函数,其必须被忽略
const _onRejected = _.isFunction(onRejected) ? onRejected : void 0;
let childResolve;
let childReject;
const childPromise = new MyPromise((resolve, reject) => {
childResolve = resolve;
childReject = reject;
});
// then 方法可以被同一个 promise 调用多次
this._thenCallbacks.push([_onFulfilled, _onRejected, childResolve, childReject]);
return childPromise;
}
_callThenCallbacks
用于处理在promise
状态改变后处理then
回调函数队列. 在处理每一个then
回调函数后, 还需要对于then
回调函数返回的结果, 结合当前的promise
状态,调整当前then
函数返回的promise2
的状态:
// 调用then回调函数队列
_callThenCallbacks() {
if (_.isEmpty(this._thenCallbacks)) {
return;
}
this._thenCallbacks.forEach(([onFulfilled, onRejected, childResolve, childReject]) => {
try {
if (this._status === FULFILLED && !onFulfilled) {
// 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
childResolve(this._value);
return;
}
if (this._status === REJECTED && !onRejected) {
// 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因
childReject(this._reason);
}
let x;
if (this._status === REJECTED && onRejected) {
// 当 promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次回调
// 其第一个参数为 promise 的拒因
// 必须被作为函数调用(即没有 this 值)
x = onRejected(this._reason);
} else if (this._status === FULFILLED && onFulfilled) {
// 当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调
// 其第一个参数为 promise 的终值
// 必须被作为函数调用(即没有 this 值)
x = onFulfilled(this._value);
}
// 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程
this._resolvePromise(x, childResolve, childReject);
} catch (error) {
childReject(error);
}
});
}
其中_resolvePromise
代表Promise解决过程, 将在下文说明.
Promise解决过程
规范描述
Promise解决过程是一个抽象的操作,其需输入一个promise
和一个值,我们表示为[[Resolve]](promise, x)
,如果x
有then
方法且看上去像一个Promise,解决程序即尝试使promise
接受x
的状态;否则其用x
的值来执行 promise 。
这种thenable
的特性使得Promise
的实现更具有通用性:只要其暴露出一个遵循 Promise/A+ 协议的then
方法即可;这同时也使遵循 Promise/A+ 规范的实现可以与那些不太规范但可用的实现能良好共存。
运行[[Resolve]](promise, x)
需遵循以下步骤:
-
x
与promise
相等
如果x
为Promise,则使promise 接受
x`的状态:- 如果
x
处于等待态,promise
需保持为等待态直至x
被执行或拒绝 - 如果
x
处于执行态,用相同的值执行promise
- 如果
x
处于拒绝态,用相同的据因拒绝promise
- 如果
-
x
为对象或函数
如果x
为对象或者函数:- 把
x.then
赋值给then
- 如果取
x.then
的值时抛出错误e
,则以e
为据因拒绝promise
- 如果
then
是函数,将x
作为函数的作用域this
调用之。传递两个回调函数作为参数,第一个参数叫做resolvePromise
,第二个参数叫做rejectPromise
:- 如果
resolvePromise
以值y
为参数被调用,则运行[[Resolve]](promise, y)
- 如果
rejectPromise
以据因r
为参数被调用,则以据因r
拒绝promise
- 如果
resolvePromise
和rejectPromise
均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用 - 如果调用
then
方法抛出了异常e
:- 如果
resolvePromise
或rejectPromise
已经被调用,则忽略之 - 否则以
e
为据因拒绝promise
- 如果
- 如果
then
不是函数,以x
为参数执行promise
- 如果
- 把
-
如果
x
不为对象或者函数,以x为参数执行
promise`
如果一个promise
被一个循环的thenable
链中的对象解决,而[[Resolve]](promise, thenable)
的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励施者检测这样的递归是否存在,若检测到存在则以一个可识别的TypeError
为据因来拒绝promise
.
代码实现
函数_resolvePromise
实现, 采用代码注释说明:
// Promise 解决过程
_resolvePromise(x, childResolve, childReject) {
// x 与 promise 相等
if (x === this) {
// 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
throw new TypeError("You cannot resolve a promise with itself");
}
// 如果 x 为 Promise ,则使 promise 接受 x 的状态
if (x instanceof MyPromise) {
// 如果 x 处于等待态
console.log('======PENDING===', x._status);
if (x._status === PENDING) {
// promise 需保持为等待态直至 x 被执行或拒绝
x.then(childResolve, childReject);
return;
}
// 如果 x 处于执行态
if (x._status === FULFILLED) {
// 用相同的值执行 promise
childResolve(x._value);
return;
}
// 如果 x 处于执行态
if (x._status === REJECTED) {
// 用相同的值执行 promise
childReject(x._reason);
return;
}
}
// x 为对象或函数
if (_.isObject(x) || _.isFunction(x)) {
// 把 x.then 赋值给 then
let then;
try {
then = x.then;
} catch (error) {
// 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
// 其实这里不需要捕获, 因为最外层有捕获, 这里为了保持跟规范一致
childReject(error);
return;
}
// 如果 then 是函数
if (_.isFunction(then)) {
// 将 x 作为函数的作用域 this 调用之
let called = false;
try {
then.call(x, (y) => {
// 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,
// 则优先采用首次调用并忽略剩下的调用
if (called) {
return;
}
called = true;
// 如果 resolvePromise 以值 y 为参数被调用
this._resolvePromise(y, childResolve, childReject);
}, (r) => {
// 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
if (called) {
return;
}
called = true;
// 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
childReject(r);
});
} catch (error) {
// 如果调用 then 方法抛出了异常 e
// 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
if (called) {
return;
}
// 否则以 e 为据因拒绝 promise
childReject(error);
}
return;
}
// 如果 then 不是函数, 以 x 为参数执行 promise
childResolve(x);
return;
}
// 如果 x 不为对象或者函数, 以 x 为参数执行 promise
childResolve(x);
}
全部代码
const _ = require('lodash');
const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2;
class MyPromise {
constructor(executor) {
if (!(this instanceof MyPromise)) {
throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
}
if (!_.isFunction(executor)) {
throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
}
this._status = PENDING;
this._value = undefined;
this._thenCallbacks = [];
this._reason = undefined;
try {
executor((...args) => this._resolve(...args), (...args) => this._reject(...args));
} catch(e) {
this._reject(e);
}
}
_resolve(value) {
// 只有在执行环境堆栈仅包含平台代码时才可被调用
process.nextTick(() => {
if (this._status !== PENDING) {
return;
}
this._status = FULFILLED;
this._value = value;
// 如果then的回调函数onFulfilled, onRejected为函数的话, 需要
// 在 promise 执行结束前其不可被调用,当 promise 执行结束后其必须被调用
// 其调用次数不可超过一次
// 只有在执行环境堆栈仅包含平台代码时才可被调用
// process.nextTick(() => this._callThenCallbacks());
this._callThenCallbacks();
});
}
_reject(reason) {
// 只有在执行环境堆栈仅包含平台代码时才可被调用
process.nextTick(() => {
if (this._status !== PENDING) {
return;
}
this._status = REJECTED;
this._reason = reason;
// 如果then的回调函数onFulfilled, onRejected为函数的话, 需要
// 在 promise 执行结束前其不可被调用,当 promise 执行结束后其必须被调用
// 其调用次数不可超过一次
// 只有在执行环境堆栈仅包含平台代码时才可被调用
// process.nextTick(() => this._callThenCallbacks());
this._callThenCallbacks();
});
}
// 调用then回调函数队列
_callThenCallbacks() {
if (_.isEmpty(this._thenCallbacks)) {
return;
}
this._thenCallbacks.forEach(([onFulfilled, onRejected, childResolve, childReject]) => {
try {
if (this._status === FULFILLED && !onFulfilled) {
// 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
childResolve(this._value);
return;
}
if (this._status === REJECTED && !onRejected) {
// 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因
childReject(this._reason);
}
let x;
if (this._status === REJECTED && onRejected) {
// 当 promise 被拒绝执行时,所有的 onRejected 需按照其注册顺序依次回调
// 其第一个参数为 promise 的拒因
// 必须被作为函数调用(即没有 this 值)
x = onRejected(this._reason);
} else if (this._status === FULFILLED && onFulfilled) {
// 当 promise 成功执行时,所有 onFulfilled 需按照其注册顺序依次回调
// 其第一个参数为 promise 的终值
// 必须被作为函数调用(即没有 this 值)
x = onFulfilled(this._value);
}
// 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程
this._resolvePromise(x, childResolve, childReject);
} catch (error) {
childReject(error);
}
});
}
// Promise 解决过程
_resolvePromise(x, childResolve, childReject) {
// x 与 promise 相等
if (x === this) {
// 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
throw new TypeError("You cannot resolve a promise with itself");
}
// 如果 x 为 Promise ,则使 promise 接受 x 的状态
if (x instanceof MyPromise) {
// 如果 x 处于等待态
console.log('======PENDING===', x._status);
if (x._status === PENDING) {
// promise 需保持为等待态直至 x 被执行或拒绝
x.then(childResolve, childReject);
return;
}
// 如果 x 处于执行态
if (x._status === FULFILLED) {
// 用相同的值执行 promise
childResolve(x._value);
return;
}
// 如果 x 处于执行态
if (x._status === REJECTED) {
// 用相同的值执行 promise
childReject(x._reason);
return;
}
}
// x 为对象或函数
if (_.isObject(x) || _.isFunction(x)) {
// 把 x.then 赋值给 then
let then;
try {
then = x.then;
} catch (error) {
// 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
// 其实这里不需要捕获, 因为最外层有捕获, 这里为了保持跟规范一致
childReject(error);
return;
}
// 如果 then 是函数
if (_.isFunction(then)) {
// 将 x 作为函数的作用域 this 调用之
let called = false;
try {
then.call(x, (y) => {
// 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,
// 则优先采用首次调用并忽略剩下的调用
if (called) {
return;
}
called = true;
// 如果 resolvePromise 以值 y 为参数被调用
this._resolvePromise(y, childResolve, childReject);
}, (r) => {
// 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
if (called) {
return;
}
called = true;
// 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
childReject(r);
});
} catch (error) {
// 如果调用 then 方法抛出了异常 e
// 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
if (called) {
return;
}
// 否则以 e 为据因拒绝 promise
childReject(error);
}
return;
}
// 如果 then 不是函数, 以 x 为参数执行 promise
childResolve(x);
return;
}
// 如果 x 不为对象或者函数, 以 x 为参数执行 promise
childResolve(x);
}
then(onFulfilled, onRejected) {
// 如果 onFulfilled 不是函数,其必须被忽略
const _onFulfilled = _.isFunction(onFulfilled) ? onFulfilled : void 0;
// 如果 onRejected 不是函数,其必须被忽略
const _onRejected = _.isFunction(onRejected) ? onRejected : void 0;
let childResolve;
let childReject;
const childPromise = new MyPromise((resolve, reject) => {
childResolve = resolve;
childReject = reject;
});
// then 方法可以被同一个 promise 调用多次
this._thenCallbacks.push([_onFulfilled, _onRejected, childResolve, childReject]);
return childPromise;
}
}
module.exports = MyPromise;