Promise
是 es6 引入的异步处理方案,让我们可以采用链式的写法注册回调函数,摆脱多层异步回调函数嵌套的情况,使代码更加简洁。而理解 Promise
内部实现原理也十分重要,我们可以从简单的模型开始,考虑不同的边界情况,一步一步的往最终结果实现。
一个简单的雏形
如下我们可以新建一个 Promise
对象, 然后马上执行成功的回调。
var a = new Promise(function(resolve) {
resolve(1);
})
a.then(x => console.log(x))
复制代码
可以看出, Promise
是一个构造函数,接收一个函数参数,其中函数参数的参数 resolve
在Promise
构造函数内部实现。构造函数有一个 then
的方法,注册成功的回调, 在调用 resolve
时执行。 因此可以得到如下一个简单的模型:
function _Promise(fn) {
var self = this;
this.value = null;
this.callbacks = [];
this.then = function(onFulfilled) {
// 注册一个成功的回调函数
this.callbacks.push(onFulfilled);
}
function resolve(value) {
self.value = value;
// 让所有回调函数进入下一个事件循环执行
setTimeout(function(){
self.callbacks.forEach(function(callback) {
callback(value);
})
},0);
}
fn(resolve)
}
复制代码
构造函数里面的属性 value
表示成功的最终值, callbacks
表示通过 then
注册的成功回调方法,类型是一个数组是因为 Promise
对象支持注册多个成功回调函数。在 resolve
中加入 setTimeout
延时是让所有回调函数在下一轮事件循环中执行,从而保证所有在当前执行队列的回调函数注册成功。
引入状态
前面实现的雏形可以让当前执行队列的回调函数成功执行,但是在下一轮或者之后注册的回调函数将无效。比如:
a.then(x => console.log(x))
setTimeout(function(){
a.then(x => console.log(x+1))
}, 1000);
复制代码
上面只会输出第一个回调结果。所以,我们需要引入一个状态属性 state
, 表示 Promise
对象当前的状态。当状态为 pending
的时候,注册的回调函数才压进 callbacks
中。当调用 resolve
后状态变为已解决 fulfilled
, 此时通过 then
注册的成功回调会马上执行。如下:
function _Promise(fn) {
var self = this;
this.state = 'pending';
this.value = null;
this.callbacks = [];
this.then = function(onFulfilled) {
if(this.state === 'pending') {
this.callbacks.push(onFulfilled);
}else {
onFulfilled(this.value);
}
}
function resolve(value) {
self.state = 'fulfilled';
self.value = value;
// 让所有回调函数进入下一个事件循环执行
setTimeout(function(){
self.callbacks.forEach(function(callback) {
callback(value);
})
},0);
}
fn(resolve)
}
复制代码
链式调用
我们知道原生的 Promise
可以支持链式调用,如下:
var a = new Promise(function(resolve) {
resolve(1);
})
a.then(x => {
console.log(x);
return x+1;
}).then(x => console.log(x))
复制代码
可以看出第一个 Promise
对象回调中返回的值会最为新对象回调的参数,相当于返回一个立即 resovle(前者返回值)
的新 Promise
对象, 所以上面会输出1和2。
现在,我们就可以把 then
函数修改成返回一个新的 Promise
对象, 并且和当前的 Promise
对象做关联。如下:
function _Promise(fn) {
var self = this;
this.state = 'pending';
this.value = null;
this.callbacks = [];
this.then = function(onFulfilled) {
// 返回一个新的Promise对象
return new _Promise(function(resolve) {
handleCallback({
onFulfilled: onFulfilled || null,
resolve: resolve // 让当前的promise对象和新的promise对象关联
})
})
}
function handleCallback(callback) {
if(self.state === 'pending') {
self.callbacks.push(callback);return;
}
var res = callback.onFulfilled(self.value);
// 调用新的promise对象的resolve
callback.resolve(res);
}
function resolve(value) {
self.state = 'fulfilled';
self.value = value;
// 让所有回调函数进入下一个事件循环执行
setTimeout(function(){
self.callbacks.forEach(function(callback) {
handleCallback(callback);
})
},0);
}
fn(resolve)
}
复制代码
有上面代码可以看出, handleCallback
方法是关联两个就行 Promise
对象的关键,该方法的参数是一个对象,对象的 onFulfilled
属性是老 Promise
对象的回调函数, resolve
属性是新对象的构造函数的 resolve
方法,也可以说是新对象的 resolve
方法。因为构造函数的 resolve
函数是一个闭包,里面的 self
保存的是对应实例化的 Promise
对象。
当第一个对象的 onFulfilled
函数为空, 直接把一个对象的终值 value
作为第二个对象的 resolve
参数。
var a = new Promise(function(resolve) {
resolve(2);
})
a.then().then(x => console.log(x)) // => 2
复制代码
于是 handleCallback
函数修改成:
function handleCallback(callback) {
if(self.state === 'pending') {
self.callbacks.push(callback);return;
}
if(!callback.onFulfilled) {
callback.resolve(self.value);return;
}
var res = callback.onFulfilled(self.value);
// 调用新的promise对象的resolve
callback.resolve(res);
}
复制代码
我们前面提到第一个对象的回调函数返回值等于第二个对象的 resolve
参数,它等同于下面形式:
var a = new Promise(function(resolve) {
resolve(1);
})
a.then(x => {
return new Promise(function(resolve){
resolve(x+1)
})
}).then(x => console.log(x))
复制代码
于是要考虑调用第一个对象回调会返回 thenable
对象的情况,这个时候应该把由 a.then()
创建的对象的 resolve
对象这个 thenable
对象的成功回调, 状态受到里面 thenable
对象的状态影响, 所以终值始终等于这个 thenable
对象的终值。于是,resolve
修改成:
function resolve(endValue) {
if(endValue && (typeof endValue === 'object') && typeof endValue.then === 'function') {
// 让新的promise对象的resolve作为thenable对象的成功回调
endValue.then(resolve);
return;
}
self.state = 'fulfilled';
self.value = endValue;
// 让所有回调函数进入下一个事件循环执行
setTimeout(function(){
self.callbacks.forEach(function(callback) {
handleCallback(callback);
})
},0);
}
复制代码
失败处理
和成功 fulfilled
的处理逻辑一样,我们引入失败的状态 rejected
和失败回调 onRejected
。
function _Promise(fn) {
var self = this;
this.state = 'pending';
this.value = null;
this.callbacks = [];
this.then = function(onFulfilled, onRejected) {
// 返回一个新的Promise对象
return new _Promise(function(resolve, reject) {
handleCallback({
onFulfilled: onFulfilled || null,
onRejected: onRejected || null,
resolve: resolve,
reject: reject
})
})
}
function handleCallback(callback) {
if(self.state === 'pending') {
self.callbacks.push(callback);return;
}
var cb = self.state === 'fulfilled' ? callback.onFulfilled : callback.onRejected;
if(cb === null) {
cb = self.state === 'fulfilled' ? callback.resolve : callback.reject;
cb(self.value);
return;
}
// 加入try-catch防止执行回调出错
try {
var res = cb(self.value);
callback.resolve(res);
}catch(e) {
callback.reject(e);
}
}
function resolve(endValue) {
if(endValue && (typeof endValue === 'object') && typeof endValue.then === 'function') {
endValue.then(resolve, reject);
return;
}
self.state = 'fulfilled';
self.value = endValue;
excute();
}
function reject(reason) {
self.state = 'rejected';
self.value = reason;
excute();
}
function excute() {
// 让所有回调函数进入下一个事件循环执行
setTimeout(function(){
self.callbacks.forEach(function(callback) {
handleCallback(callback);
})
},0);
}
fn(resolve, reject)
}
复制代码
加入 try-catch
保证在执行回调出错的时候能捕捉得到。如果执行回调成功,新的 Promise
总是成功 fulfilled
的,不管你之前的 Promise
对象是调用 resolve
还是 reject
。
总结
理解 Promise
源码的关键点如下:
-
then
函数在Promise
为pending
状态时为注册回调,统一压到一个回调数组,所以我们会发现上面的测试例子的callbacks
都是空数组,然后在resolve
或者reject
时才会统一执行。在其他状态注册都会直接执行。 -
then
函数返回一个新的Promise
对象,两个对象通过resolve(前者对象的终值)
关联起来。