Promise
代表一个异步操作的结果,使用Promise
我们可以:
- 摆脱回调地狱;
- 更易读的代码,函数参数、返回值一目了然。比较下面两个函数:
// 额外的callback参数让人对函数的输入和输出类型感到困惑
function asyncFn(argu: number, callback): void {
let value: string = '';
...
callback(err, value)
}
function asyncFn(argu: number): Promise {
return new Promise((resolve, reject) => {
let value: string = '';
...
if (err) {
reject(err)
}
resolve(value)
})
}
- 更容易地处理异步错误;
通过尝试实现Promise
,可以对Promise
的行为有更深的理解。
实现Promise
先从阅读规范开始。
规范解读
Promise
规范由3个部分组成:
- Promise状态
- then方法
- Promise Resolution Procedure
下面依次对这3部分进行解读,其中忽略了异常处理部分和一些细节。
Promise状态
-
promise
的状态必为pending
、fulfilled
和rejected
这三个状态中的一个。 - 当
promise
为pending
状态时,可能会转变为fulfilled
状态或rejected
状态。 - 当
promise
为fulfilled
状态时,必须拥有一个value
,并且不会再变化。 - 当
promise
为rejected
状态时,必须拥有一个reason
,并且不会再变化。
then方法
-
promise
必须提供一个then
方法,通过then
方法可获取最终的value
或reason
。 -
then
方法接收两个参数,并返回一个promise
:
promise2 = promise1.then(onFulfilled?, onRejected?)
-
onFulfilled
和onRejected
是可选参数。 - 如果
onFulfilled
是一个函数,它将仅在promise1
变为fulfilled
状态时被调用一次,value
是它的第一个参数。 - 如果
onRejected
是一个函数,它将仅在promise1
变为rejected
状态时被调用一次,reason
是它的第一个参数。 - 当
onFulfilled
不是函数,并且promise1
是fulfilled
状态时,promise2
的状态必须也是fulfilled
,并且拥有和promise1
一样的value
。引用MDN
的话说,即,当onFulfilled
不是函数时,会用identity function
(返回它接收的参数)替代。 - 当
onRejected
不是函数,并且promise1
是rejected
状态时,promise2
的状态必须也是rejected
,并且拥有和promise1
一样的reason
。引用MDN
的话说,即,当onRejected
不是函数时,会用thrower function
(抛出它接收的参数)替代。 -
onFulfilled
和onRejected
必须被异步调用。以避免unleash Zalgo
。也就是要保证onFulfilled
和onRejected
始终是异步调用的,避免出现有时异步有时同步的情况。比如下面这个例子,console.log
始终会被异步执行。
new Promise((resolve, reject) => {
if (Math.random() >= 0.5) {
setTimeout(() => resolve(0), 5000)
return;
}
resolve(1)
}).then(console.log)
-
onFulfilled
和onRejected
必须被当作函数调用(没有this
)。 -
then
方法可以在同一个promise
上多次调用。相应的onFulfilled/onRejected
被调用的顺序和原本调用then
方法的顺序相同。 -
onFulfilled
和onRejected
任一个抛出错误e
,promise2
会变成rejected
状态,并以错误e
作为reason
。 -
onFulfilled
和onRejected
任一个返回x
,promise2
会执行Promise Resolution
过程:[[Resolve]](promise2, x)
。
Promise Resolution过程
Promise Resolution
过程即promise.resolve(x)
的过程,规范中标记为[[Resolve]](promise, x)
。
首先这里涉及一个概念:thenable
,如果x
是object
或者function
类型,并且有then
方法,那么x
就是thenable
,显然,promise
对象是thenable
。
如果x
不是thenable
,Promise Resolution
过程就是将promise
变为fulfilled
状态,并以x
作为fulfilled value
。
如果x
是thenable
,promise
会尝试采用x
的状态。即,x
是pending
状态时,promise
也是pending
状态;如果x
变成fulfilled
状态,promise
也变成fulfilled
状态,并用x
的fulfilled value
作为promise
的fulfilled value
;如果x
变成rejected
状态,promise
也变成rejected
状态,并用x
的rejected reason
作为promise
的rejected reason
。
因此,会存在thenable chain
的情况:
thenable chain
的长度不会被限制,可以链任意多的thenable
。
但是,考虑到
[[Resolve]](promise1, promise1)
会导致无限循环,所以这种情况下应该抛出TypeError
。如:
以下3种场景都会执行[[Resolve]](promise, x)
过程
// 1.
promise.resolve(x);
// 2.
promise = new Promise(function(resolve, reject) {
resolve(x);
})
// 3.
promise = promise1.then(function() {
return x;
})
实现
为了便于理解,将promise
实现拆分成若干步骤,逐步实现。
状态机
由于Promise
本身是一个状态机,所以从这里开始。Promise
初始状态是pending
,在fulfill
或reject
时,更新状态。
function Promise() {
var value, reason, state = 'pending';
function beFulfilled(result) {
state = 'fulfilled';
value = result;
}
function beRejected(error) {
state = 'rejected';
reason = error;
}
}
Promise Resolution过程
Promise Resolution Procedure
是一个递归的过程,直至所有thenable
都完成。
function Promise(fn) {
var value, reason, state = 'pending';
function beFulfilled(result) {
state = 'fulfilled';
value = result;
}
function beRejected(error) {
state = 'rejected';
reason = error;
}
function doResolution(x) {
var type = typeof x
if ((type === 'object' || type === 'function') && typeof x.then === 'function') {
x.then(function(val) {
doResolution(val);
}, function(err) {
beRejected(err)
})
}
beFulfilled(x)
}
fn(doResolution, beRejected)
}
then方法onFulfilled/onRejected执行
then
方法可能在fulfilled/rejected
之前或者之后被调用,也可以被多次调用。如果调用then
时,promise
还处于pending
状态,应该先将onFulfilled/onRejected
暂存起来。在promise
变为fulfilled/rejected
之后,依次执行暂存的onFulfilled/onRejected
。
function Promise(fn) {
var value,
reason,
state = 'pending',
handlers = [];
function beFulfilled(result) {
state = 'fulfilled';
value = result;
handlers.forEach(handle);
handlers = [];
}
function beRejected(error) {
state = 'rejected';
reason = error;
handlers.forEach(handle);
handlers = [];
}
function doResolution(x) {
var type = typeof x
if ((type === 'object' || type === 'function') && typeof x.then === 'function') {
x.then(function(val) {
doResolution(val);
}, function(err) {
beRejected(err)
})
}
beFulfilled(x)
}
function handle(handler) {
if (state === 'pending') {
handlers.push(handler);
return;
} else if (state === 'fulfilled') {
handler.onFulfilled.call(null, value)
} else if (state === 'rejected') {
handler.onRejected.call(null, reason)
}
}
this.then = function(onFulfilled, onRejected) {
handle({
onFulfilled,
onRejected,
});
}
fn(doResolution, beRejected)
}
then方法返回promise
then
方法需要返回一个promise
,返回的promise
依赖onFulfilled/onRejected
执行结果,所以将返回的promise
的resolve/reject
和then
方法的onFulfilled/onRejected
保存在一起,在onFulfilled/onRejected
执行后调用resolve/reject
。
function Promise(fn) {
var value,
reason,
state = 'pending',
handlers = [];
function beFulfilled(result) {
state = 'fulfilled';
value = result;
handlers.forEach(handle);
handlers = [];
}
function beRejected(error) {
state = 'rejected';
reason = error;
handlers.forEach(handle);
handlers = [];
}
function doResolution(x) {
var type = typeof x
if ((type === 'object' || type === 'function') && typeof x.then === 'function') {
x.then(function(val) {
doResolution(val);
}, function(err) {
beRejected(err)
})
}
beFulfilled(x)
}
function handle(handler) {
if (state === 'pending') {
handlers.push(handler);
return;
} else if (state === 'fulfilled') {
try {
handler.resolve(handler.onFulfilled.call(null, value))
} catch(e) {
handler.reject(e)
}
} else if (state === 'rejected') {
try {
handler.resolve(handler.onRejected.call(null, reason))
} catch(e) {
handler.reject(e);
}
}
}
this.then = function(onFulfilled, onRejected) {
return new Promise(function(resolve, reject) {
handle({
onFulfilled,
onRejected,
resolve,
reject,
});
})
}
fn(doResolution, beRejected)
}
至此,基本实现了promise
,最后,补充完整错误处理和其他细节,代码如下:
function Promise(fn) {
var value,
reason,
state = 'pending',
handlers = [],
/**
* 当resolve/reject被多次调用时,只有第一次调用有效。
* 如:
* new Promise((resolve, reject) => {
* resolve(new Promise(r => setTimeout(() => r(1), 5000)))
* resolve(new Promise(r => setTimeout(() => r(2), 3000)))
* }).then(console.log)
**/
invoked = false,
self = this;
// 考虑typeof null === 'object',所以判断类型是否为object或function,不能简单通过typeof判断
function typeOf(obj) {
return Object.prototype.toString.call(obj).match(/^\[object\s+(\w+)\]$/)[1].toLowerCase();
}
function once(func) {
return function() {
if (invoked) return;
invoked = true;
func.apply(null, arguments);
}
}
function beFulfilled(result) {
state = 'fulfilled';
value = result;
handlers.forEach(handle);
handlers = [];
}
function beRejected(error) {
state = 'rejected';
reason = error;
handlers.forEach(handle);
handlers = [];
}
function doResolution(x) {
/**
* 如果onFulfilled/onRejected被多次调用,只有第一次调用有效。
* 如:
* new Promise((resolve, reject) => {
* resolve({
* then(onFulfilled, onRejected) {
* onFulfilled(new Promise(r => setTimeout(() => r(1), 5000)));
* onFulfilled(new Promise(r => setTimeout(() => r(2), 3000)));
* }
* })
* }).then(console.log)
*/
var called = false;
if (self === x) {
beRejected(new TypeError('Chaining cycle detected'));
return;
}
var type = typeOf(x);
if (type === 'object' || type === 'function') {
try {
var then = x.then;
if (typeOf(then) === 'function') {
try {
then.call(x, function(val) {
if (called) return;
called = true;
doResolution(val);
}, function(err) {
if (called) return;
called = true;
beRejected(err);
})
} catch(e) {
if (called) return;
called = true;
beRejected(e);
}
} else {
beFulfilled(x);
}
} catch(e) {
beRejected(e);
}
} else {
beFulfilled(x);
}
}
function handle(handler) {
if (state === 'pending') {
handlers.push(handler);
return;
} else if (state === 'fulfilled') {
var x, onFulfilled = typeOf(handler.onFulfilled) === 'function' ? handler.onFulfilled : function(v) {
return v;
};
setTimeout(function() {
try {
x = onFulfilled(value);
} catch(e) {
handler.reject(e);
return;
}
handler.resolve(x);
});
} else if (state === 'rejected') {
var x, onRejected = typeOf(handler.onRejected) === 'function' ? handler.onRejected : function(r) {
throw r;
};
setTimeout(function() {
try {
x = onRejected(reason);
} catch(e) {
handler.reject(e);
return;
}
handler.resolve(x);
});
}
}
this.then = function(onFulfilled, onRejected) {
return new Promise(function(resolve, reject) {
handle({
onFulfilled,
onRejected,
resolve,
reject,
});
});
};
fn(once(doResolution), once(beRejected));
}
完整代码已经通过Promises/A+ Compliance Test Suite全部测试用例。
// 测试文件,我将上面实现的Promise取名Qromise
const Qromise = require('../Qromise')
describe("Promises/A+ Tests", function () {
require("promises-aplus-tests").mocha({
resolved(value) {
return new Qromise(resolve => resolve(value));
},
rejected(reason) {
return new Qromise((...[, reject]) => reject(reason));
},
deferred() {
const obj = {}
obj.promise = new Qromise((resolve, reject) => {
obj.resolve = value => resolve(value);
obj.reject = reason => reject(reason);
})
return obj
},
});
});