今天,我带着大家一步一步跟着规范实现一个自己的Promise
,大家可以对照我的第二篇文章Promise介绍--规范篇或官方规范来一一学习。
Promise
内部有三个固定的状态,我们在文件中提前定义。
const PENDING = "pending"
const FULFILLED = "fulfilled"
const REJECTED = "rejected"
首先,是构造器constructor
。
constructor(resolver){
this._status = PENDING; // 保存内部的状态
this._result = undefined; // 保存promise对象fulfill或reject的最终结果
this._childArr = []; // 调用then方法创建的子promise对象
this._fulfillArr = []; // 调用then方法添加的onFulfilled方法
this._rejectArr = []; // 调用then方法添加的onRejected方法
if (resolver == noop) { return}; // then方法内部创建promise时间使用
// 如果resolver不是函数,则抛出TypeError错误
if (!isFunction(resolver)) {
throw new TypeError('参数必须为function');
};
// 如果直接调用Promise而非通过new关键词创建,同样抛出TypeError错误
if (this instanceof Promise) {
initPromise(this, resolver)
} else {
throw new TypeError('Promise不可以直接作为函数调用')
}
}
当参数传递正确时,才真正初始化Promise
对象,我提到了一个单独的函数initPromise
中,如下:
function initPromise(promise, resolver){
// 当调用传入的resolver函数抛出异常,则reject当前promise
try {
resolver(function(value){
// 封装的内部函数,处理resolve(value),也就是Promise Resolution Procedure
resolve(promise, value);
}, function(reason){
// 封装的内部函数,处理reject(reason)
reject(promise, reason);
});
} catch (e) {
reject(promise, e);
}
}
当我们执行new Promise((resolve){resolve(5)})
时,会走resolve(promise, value)
。接下来我们实现一下Promise Resolution Procedure
。
function resolve(promise, value){
// 2.3.1 如果promise和value指向同一对象
if (promise === value) {
reject(promise, new TypeError('不可以resolve Promise实例本身'))
// 2.3.2 如果value是一个promise
} else if (value instanceof Promise) {
// 2.3.2.2 如果value处于fulfilled状态,则使用相同的value值fulfill promise。
if (value._status == FULFILLED) {
fulfill(promise, value._result);
// 2.3.2.3 如果value处于rejected状态,则使用相同的reason值reject promise。
} else if (value._status == REJECTED) {
reject(promise, value._result);
// 2.3.2.1 如果value处于pending状态,则promise同样pending并直到value状态改变。
// 重新把resolve(promise, value)添加到队列,asyncCall封装了一下异步调用
} else {
asyncCall(resolve, [promise, value]);
}
// 2.3.3 如果x是一个object或function
} else if (isObjectOrFunction(value)){
// 2.3.3.2 如果获取value.then的值时抛出异常,则通过该异常reject promise
try{
let then = value.then; // 2.3.3.1 使then等于value.then
// 2.3.3.3 如果then是一个函数
if (isFunction(then)) {
try{
handleThenable(promise, value, then);
} catch (e) {
reject(promise, e);
}
// 2.3.3.4 如果then不是一个函数
} else {
fulfill(promise, value);
}
} catch (e) {
reject(promise, e);
}
// 2.3.4 value不是对象或函数
} else {
fulfill(promise, value);
}
}
因为value.then
是函数时,处理情况同样很多且比较杂乱,我单独把这部分处理提取到handleThenable
函数中。实现如下:
function handleThenable(promise, value, then){
let settled = false; // 是否fulfilled或rejected
try {
// 2.3.3.3 如果then是一个函数,则把value作为函数中this指向来调用它
then.call(value, (otherValue)=>{
// 2.3.3.3.3
if (settled) { return};
// 2.3.3.3.1 如果resolvePromise通过传入y来调用,则执行resolve(promise, y)
resolve(promise, otherValue);
settled = true;
}, (reason)=>{
// 2.3.3.3.3
if (settled) { return};
// 2.3.3.3.2 如果rejectPromise 通过传入原因r来调用,则传入r来reject promise
reject(promise, reason);
settled = true;
})
// 2.3.3.3.4 如果调用then抛出异常e
} catch (e) {
// 2.3.3.3.4.1 如果resolvePromise或rejectPromise已经调用,则忽略
if (settled) { return};
settled = true;
// 2.3.3.3.4.2 否则,则传入e来reject promise
reject(promise, e)
}
}
以上,基本就是完整的我对Promise Resolution Procedure
的实现。
还有一个非常重要的方法就是then
方法,接下来我们看一下它是如何实现的。then
内部总体上分为两种情况,一种是当前promise
对象状态已经变为fulfilled
或rejected
,此时则直接把响应的回调函数添加到异步队列中,另一种情况是当前promise
对象状态还是pending
,此时则把响应的回调函数依次添加到数组中。
then(onFulfilled, onRejected){
let child = new Promise(noop);
// 如果当前对象状态已经改变,则直接根据状态调用响应的回调
if (this._status !== PENDING) {
if (this._status == FULFILLED) {
// 2.2.4 异步调用
asyncCall(()=>{
dealThen(this, child, onFulfilled);
})
} else {
// 2.2.4 异步调用
asyncCall(()=>{
dealThen(this, child, onRejected);
})
}
// 如果当前对象处于pending状态,则把onFulfilled, onRejected添加到
} else {
this._childArr.push(child);
this._fulfillArr.push(onFulfilled);
this._rejectArr.push(onRejected);
}
// 返回一个新的promise对象
return child;
}
具体处理逻辑我放到了一个新的函数dealThen
中,注意它是异步调用的。所以用asyncCall
方法包装了一下。
// 处理then
function dealThen(promise, child, x){
// onFulfilled或onRejected是一个函数
if (isFunction(x)) {
// 2.2.7.1 如果onFulfilled或onRejected返回了一个值value,则执行resolve(child, value)
try {
resolve(child, x(promise._result));
// 2.2.7.2 如果onFulfilled或onRejected抛出了异常e,则reject child并传入原因e
} catch (e) {
reject(child, e);
}
} else {
try{
// 2.2.1.1 如果onFulfilled不是一个函数,则忽略
if (promise._status == FULFILLED) {
fulfill(child, promise._result);
// 2.2.1.2 如果onRejected不是一个函数,则忽略
} else {
reject(child, promise._result);
}
} catch (e) {
reject(child, e);
}
}
}
从上面的代码中我们看到有两个比较重要的方法——fulfill
和reject
,它们才是真正改变promise
状态并调用相应回调的地方。
function fulfill(promise, value){
// 如果状态已经不是pending,则直接return
if (promise._status !== PENDING) { return };
// 设置状态为fulfilled,并设置最终结果
promise._status = FULFILLED;
promise._result = value;
// 异步依次调用添加的onFulfilled方法
if (promise._fulfillArr.length > 0) {
// 2.2.6.1 如果promise fulfilled,则所有的onFulfilled回调函数按照它们添加的顺序依次调用。
promise._fulfillArr.forEach((k,index)=>{
// 2.2.5 onFulfilled和onRejected必须作为函数来调用,没有this值
asyncCall(dealThen, [promise, promise._childArr[index], k])
});
}
}
function reject(promise, reason){
// 如果状态已经不是pending,则直接return
if (promise._status !== PENDING) { return };
// 设置状态为rejected,并设置最终结果
promise._status = REJECTED;
promise._result = reason;
// 异步依次调用添加的onRejected方法
if (promise._rejectArr.length > 0) {
// 2.2.6.2 如果promise rejected,则所有的onRejected回调函数按照它们添加的顺序依次调用。
promise._rejectArr.forEach((k,index)=>{
// 2.2.5 onFulfilled和onRejected必须作为函数来调用,没有this值
asyncCall(dealThen, [promise, promise._childArr[index], k])
});
}
}
当然,还有一个实例方法catch
,其实它调用的也是then
方法。
catch(onRejected){
return this.then(undefined, onRejected);
}
当然,Promise
还有四个实例方法,分别如下:
resolve
Promise.resolve = function(value){
return new Promise(function(resolve){resolve(value)})
}
reject
Promise.reject = function(reason){
return new Promise(function(resolve, reject){reject(reason)})
}
all和race的实现没有太好好思考,也没有跑测试,不知道有没有问题,而且个人感觉实现的也比较挫,是用setInterval
一直轮询查看每一个promise
实例状态有没有改变,所以就不show code了。主要内容还是讲Promises/A+
的实现。
完整的代码见我的github。
当然也有几个问题,异步我是用setTimeout
实现的,它属于macro-task
,而原生的Promise
属于micro-task
,所以这里还有待改进。
另外,在上面的实现中,我们发现resolve(promise, value)
中,在对2.3.2.1 如果value处于pending状态,则promise同样pending并直到value状态改变。我基本采用的也是setTimeout
轮询的方式实现的。之后看了es-promise
的实现,因为根据2.3.2.1此时promise
的状态和value
是同步的,所以可以把resolve
和reject
promise
分别放在value
相应状态的回调中,并假设此时与之对应的value
的child
是undefined
。如下所示:
// asyncCall(resolve, [promise, value]);
value._childArr.push(undefined);
value._fulfillArr.push((value)=>{resolve(promise, value)}); // ①
value._rejectArr.push((reason)=>{reject(promise, reason)}); // ②
此时我们还需要对fulfill
和reject
两个方法稍作改动。
function fulfill(promise, value){
if (promise._status !== PENDING) { return };
promise._status = FULFILLED;
promise._result = value;
if (promise._fulfillArr.length > 0) {
promise._fulfillArr.forEach((k,index)=>{
// 如果对应的child不是undefined,则异步调用回调
if (promise._childArr[index]) {
asyncCall(dealThen, [promise, promise._childArr[index], k])
// 如果对应的child是undefined,则直接执行回调函数①并传入value
} else {
k(value);
}
});
}
}
function reject(promise, reason){
if (promise._status !== PENDING) { return };
promise._status = REJECTED;
promise._result = reason;
if (promise._rejectArr.length > 0) {
promise._rejectArr.forEach((k,index)=>{
// 如果对应的child不是undefined,则异步调用回调
if (promise._childArr[index]) {
asyncCall(dealThen, [promise, promise._childArr[index], k])
// 如果对应的child是undefined,则直接执行回调函数②并传入reason
} else {
k(reason);
}
});
};
}
修改版见Promise1。
错误或不足之处,欢迎指正。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。