本文讲解了如何用JavaScript实现一个Promise,实现过程用到了以下的知识点
- 闭包
- 事件循环
-
typeof
的用法
学习实现Promise
之前,先看一个简单的Promise
的使用案例
var promise=new Promise(function(resolve,rejcet){
setTimeout(function(){
resolve(42);
console.log('Promise done!');
},1000);
});
promise.then(function(value){
console.log('I get the result ',value);
});
输出的结果是
Promise done!
I get the result 42
这段代码目的是执行一个异步操作(setTimeout
),把异步操作中得到的结果传递给下一个函数。
由于事件循环的原因,then
方法比resolve
方法先执行,提前把一个回调函数添加到了Promise
中,等到resolve
方法执行时,回调函数被调用。似乎resolve
执行过程并不是同步的,因为它后面的一行代码运行完才执行那个提前添加的回调函数。
根据观察可以猜测Promise
内部大概是这样:
function Promise(executor) {
var deferreds = [];
this.then = function (onFulfilled) {
deferreds.push(onFulfilled);
};
function resolve(value) {
setTimeout(function(){
deferreds.forEach(function (deferred) {
deferred(value);
});
},0);
}
executor(resolve);
}
串联Promise
Promise
串联是我们常用的功能,这也是Promise
最具魅力的地方。通过让then
方法返回Promise
对象就可以实现。
this.then = function (onFulfilled) {
return new Promise(function (resolve,rejcet) {
deferreds.push({
onFulfilled: onFulfilled || null,
resolve: resolve
});
});
};
function resolve(result) {
setTimeout(function () {
deferreds.forEach(function (deferred) {
value = deferred.onFulfilled(result);
deferred.resolve(value);
});
}, 0);
}
这里的关键是实例化Promise
时把一个新的resolve函数传递给前一个Promise
的回调队列中,可以看出串联的本质是resolve
嵌套执行。这里用到了闭包。
到这里为止,我们构建的这个Promise
已经基本可以使用了。
接着最开始的例子测试一下
promise.then(function(value){
console.log('I get the result ',value);
return value;
}).then(function(value){
console.log('I get the result ',value);
});
设想我们用Promise
封装一个http请求,请求到数据不是想要的,要处理这种情况必须在后续的回调函数里进行条件判断,这非常麻烦。所以Promise A+标准规定必须给Promise
设置状态,并且then
方法里要注册两个回调函数,根据Promise
的状态来执行相应的回调函数。
Promise的状态
Promise A+标准规定一个Promise必须处在pending
,fulfilled
,fulfilled
这三种状态中的一个,看字面意思就可以理解,pending
是未完成的状态,fulfilled
是完成的状态,fulfilled
是失败的状态。pending
可以向其他两种状态转换,但这种转换时不可逆的,而且一个Promise
的状态只能转换一次,也就是说不可以在另两种状态之间互相转换。
引入状态和失败状态的处理函数,重构一下代码
var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;
var state = PENDING,//初始状态设为pending
value = null,//存储onFulfilled和onRejected执行的结果
deferreds=[];
function resolve(result) {
if (state !== PENDING) {return;}
setTimeout(function() {
value = result;
state = FULFILLED;
done();
}, 0);
}
function reject(reason) {
if (state !== PENDING) {return;}
setTimeout(function() {
value = reason;
state = REJECTED;
done();
}, 0);
}
function done() {
setTimeout(function() {
deferreds.forEach(function(deferred) {
handle(deferred);
});
}, 0);
}
所有状态的判断和回调函数的执行交给handle
方法来做。
function handle(deferred) {
if (state === PENDING) {
deferreds.push(deferred);
}
else {
if (state === FULFILLED) {
var result = typeof deferred.onFulfilled === 'function' ? deferred.onFulfilled(value) : value;
deferred.resolve(result);
} else if (state === REJECTED) {
var reason = typeof deferred.onRejected === 'function' ? deferred.onRejected(value) : value;
deferred.reject(reason);
}
}
}
小结
本文从已知的Promise
的用法入手,运用熟悉的知识逐步深入去实现一个Promise,经过一番摸索,Promise
不再神秘,它的内部原理已经被揭晓。一些常见的Promise
用法在本文中没有被实现,比如对thenable
对象的支持,还有ES6 Promise中的catch
,all
,race
等方法,大家可以自己思考并实现。
本文中的源代码在这里。