Promise的简易实现(1)
1.Promise的日常使用
举个栗子:
function getMsg() {
return new Promise(function(resolve) {
//异步请求
http.get(url,function(msg){
resolve(msg);
})
})
}
getMsg()
.then(res => console.log(res))
2.做出Promise大致框架
function Promise(fn) {
var value = null,
callbacks = [];//callbacks为数组,因为可能同时有很多个回调
this.then = function (onFulfilled) {
//执行then函数的时候,添加所有的callback方法
callbacks.push(onFulfilled);
//return this支持链式调用
return this;
};
function resolve(value) {
//外部执行resolve函数时,将所有的callback方法依次执行
callbacks.forEach(function (callback) {
callback(value);
});
}
fn(resolve);
}
代码的大致逻辑:
- 调用then方法,将想要在Promise异步操作成功时执行的回调放入callbacks队列,其实也就是注册回调函数,可以向观察者模式方向思考;
- 创建Promise实例时传入的函数会被赋予一个函数类型的参数,即resolve,它接收一个参数value,代表异步操作返回的结果,当一步操作执行成功后,用户会调用resolve方法,这时候其实真正执行的操作是将callbacks队列中的回调一一执行;
3.实现Promise延迟机制
细心的小明发现了一个问题,如果在then方法注册回调之前,resolve函数就执行了,怎么办?比如promise内部的函数是同步函数:
function getMsg() {
return new Promise(function(resolve) {
//异步请求
resolve(123);
})
}
Promises/A+规范明确要求回调需要通过异步方式执行,用以保证一致可靠的执行顺序。因此我们要加入一些处理,保证在resolve执行之前,then方法已经注册完所有的回调。这个时候就需要我们实现resolve方法的延时机制,使用setTimeout方法:
function resolve(value) {
setTimeout(function() {
callbacks.forEach(function (callback) {
callback(value);
});
}, 0)
}
setTimeout会将callback的异步执行队列放在下一个macrotask里面,等执行了macrotask中的所有microtask后再执行下一个macrotask。具体可查询Javascript的event loop相关知识。
4.完整Promise实现
1.Promise状态的引入
在上述代码中,我们大致实现了Promise的基本功能,但小明通过敏锐的洞察力可观察到,即使在执行resolve会依次执行callback的所有注册方法,这样额外开销会非常大,这显然并不是我们想要的Promise,所以我们需要给Promise增加状态,当状态切换时,执行相应的回调方法。三种状态分别为pending、fulfilled、rejected,对应相应的回调方法。
2.Promise状态的相互转换
Promises/A+规范Promise States中明确规定了,pending可以转化为fulfilled或rejected并且只能转化一次,也就是说如果pending转化到fulfilled状态,那么就不能再转化到rejected。也就是说在Promise的状态切换是不可逆的。并且fulfilled和rejected状态只能由pending转化而来,两者之间不能互相转换。可以看下图之前的切换:
代码的思路是这样的:resolve执行时,会将状态设置为fulfilled,在此之后调用then添加的新回调,都会立即执行。
function Promise(fn) {
var state = 'pending',
value = null,
callbacks = []; //callbacks为数组,因为可能同时有很多个回调
this.then = function (onFulfilled) {
if (state === 'pending') {
callbacks.push(onFulfilled);
//return this支持链式调用
return this;
}
//假如state已经切换至其他状态,直接执行回调
onFulfilled(value);
return this;
};
function resolve(newValue) {
state = 'fulfilled';
value = newValue;
setTimeout(function () {
callbacks.forEach(function (callback) {
callback(newValue);
});
}, 0);
}
fn(resolve);
}
3.链式Promise
但聪明的小明又发现了一个问题,当在then里面执行一个回调时,假如在这里面返回一个Promise,类似于这样:
getMsg()
.then(getDetailMsg)
.then(function (detailMsg) {
// 对detailMsg的处理
});
function getDetailMsg(msg) {
return new Promise(function (resolve) {
http.get(url + msg, function(detailMsg) {
resolve(detailMsg);
});
});
}
这就是传说中的链式Promise循环,在第二个then中,我们希望它能挂钩到第一个Promise所resolve的参数。要达到我们想要的效果,需要修改then的源码,使其返回一个新的Promise。作为Promise实现最大的难题,这个我们后面继续实现。