Promise标准
1.Promise规范
Promise规范有很多,如Promise/A,Promise/B,Promise/D以及Promise/A的升级版Promise/A+。ES6 中采用了Promise/A+ 规范。
什么是 Promise/A+ 规范,推荐一篇文章Promises/A+规范(中文);
2.Promise标准解读
1.一个promise的当前状态只能是pending、fulfilled和rejected三种之一。状态改变只能是pending到fulfilled或者pending到rejected,并且状态改变不可逆的。
2.promise的then方法接收两个可选参数,表示该promise状态改变时的回调(promise.then(onFulfilled, onRejected))。then方法返回一个promise。then方法可以被同一个promise调用多次。
Promise的使用
const promise = new Promise((resolve) => {
setTimeout(()=> {
resolve(1);
}, 2000);
});
promise.then(a=> alert(a));
上面这段代码的解读:
- 构造函数接收一个
executor立即执行函数; executor立即执行函数接收一个resolve函数;promise对象的then方法绑定状态变为fulfilled时的回调,then方法也可以被称为注册函数(这句话怎么理解呢?不着急,下面会解释);resolve函数被调用时会触发then方法中注册的回调函数(这句话很重要,在我看来,能理解这句话,就能理解promise);
用大家都会用,那么,我们来实现一个最最最简单的promise
实现简单的promise
直接看下面这段代码
function MyPromise(executor){
var self = this;
this.status = "pending";
this.data = "";
this.resolveArr = [];
this.rejectArr = [];
function resolve(data){
//问题:为啥要在这里做异步处理(如过现在不能理解,等看完一定能理解的)
self.data = data
setTimeout(function(){
self.resolveArr.forEach((fn)=>{
fn(data);
});
},0);
}
function reject(){}
try{
executor(resolve,reject)
}catch(err){
reject(err)
}
}
MyPromise.prototype.then = function(resolveFn,rejectFn){
this.resolveArr.push(resolveFn);
this.rejectArr.push(rejectFn);
}
来解释一下上边这段代码
-
MyPromise接收了一个executor作为参数,在实例化的时候去执行;这个executor接收两个参数resolve和reject(注释:和原生的Promise(function(resolve,reject){})是保持一致的); -
接下来看
resolve和reject这俩参数,在MyPromise函数体内,可以看到,这两个参数其实是定义好的,可以理解为一个成功回调和一个失败的回调,和原生的Promise保持一致(在接下来的讲解中,基本以成功回调的resolve来做说明);来看下resolve函数内部做了什么?发现resolve只做了一件事,遍历了函数体内定义的resolveArr,然后执行了resolveArr了每一项,并把data参数传递进去;
那么问题来了,既然resolveArr的每一项都可以执行,那就是函数咯,我们可以看到,在函数体内,我们只定义了一个空的resolveArr,那这个函数是怎么来的呢?
- 在代码的最后面发现了一个挂载在原型上的
then方法里面有用到这个数组(在文章的前面有说到,then方法可以理解为注册方法,怎么个注册法呢?),原来在这个then方法会接收两个函数类型的参数(和原生的Promise保持一致);并把第一个参数添加到resolveArr中,第二个参数添加到rejectArr中。这也就完成了注册!
说了那么多,来测试一下,是否有效:
var test = new MyPromise(function(resolve){
setTimeout(()=>{
resolve("测试");
},1000);
});
test.then((data)=>{
console.log(data);
});
运行结果如下:

重点再次提醒:时刻记住一句话,resolve函数被调用时会触发then方法中注册的回调函数,从上面这个案例可以充分的体现;
理解了上面这个案例以后,我们来慢慢的做优化
优化一
上面提到,then方法返回一个promise。then 方法可以被同一个 promise 调用多次。来改写下then方法:
MyPromise.prototype.then = function(onResolved,onRejected){
this.resolveArr.push(onResolved);
this.rejectArr.push(onRejected);
return this;
}
只需要在then的最后return this即可,看过jquery源码的同学应该都很熟悉吧!
问题点:这样做的话,所有then方法注册的函数所接收的值都是一样的,这显然是我们不能接收的,后面还会优化
优化二
上面提到,一个promise的当前状态只能是pending、fulfilled和rejected三种之一。状态改变只能是pending到fulfilled或者pending到rejected。来改下函数体内的resolve方法:
function resolve(data){
if(self.status==="pending"){
self.status = "fulfilled";
setTimeout(function(){
self.resolveArr.forEach((fn)=>{
fn(data);
});
},0)
}
}
只需在遍历resolveArr前判断状态是否为pending,如果是,则改变状态为fulfilled,然后执行遍历,这里也解释了文章前面提到的一句话(promise对象的then方法绑定状态变为fulfilled时的回调);
针对优化一提出的问题,来做进一步的优化
根据promise/A+规范优化以上代码
文章开头对promise/A+规范的标准解读中提到(then方法返回一个promise),那我们就给then方法返回一个promise,来改写then方法:
MyPromise.prototype.then = function(onResolved,onRejected){
var self = this;
return new MyPromise(function(resolve,reject){
self.resolveArr.push(function(){
var x = onResolved(self.data);
if(x instanceof MyPromise){
//当then返回的是一个MyPromise的时候,会把resove挂在到该MyPromise的resolveArr队列中,等待该MyPromise执行对应的resolve;
x.then(resolve,reject)
}else{
//当then的返回值不是一个MyPromise的时候,直接执行resolve跳到下一个then处理;
resolve(x);
}
});
});
}
再次强调:resolve函数被调用时会触发then方法中注册的回调函数
提问:这里涉及了几个MyPromise实例?
答案:2个或者3个;
为什么呢?
来看一下,代码中有一句var x = onResolved(self.data),并在后面判断了x是不是属于MyPromise类型;从代码中也可以看出,x是函数onResolved的返回结果,那这个onResolved又是什么呢?原来就是调用then方法时所注册的函数;当x也是一个MyPromise实例的时候,这里就涉及了三个MyPromise实例,分别是(当前实例(this),then方法返回的实例,注册函数返回的实例(x));
分析
为了方便,我把当前实例称为A,then方法返回的实例称为B;
当A调用then方法的时候,直接返回了B,并在B初始化的时候,给A的resolveArr加入一个匿名函数(这里记为AResolveFn),当A中的resolve执行的时候,会去执行这个AResolveFn,在这个AResolveFn中,会去执行我们传入then中的onResolved方法,并把返回结果记x。
情况一:当x不为MyPromise
从代码中可以看出,在这种情况下,直接执行了resolve(x);这个resolve是B的resolve,那B的resolve执行了,就会触发B实例用then方法注册的方法,这样就实现了then的链式调用,并且把每次注册方法的返回值传下去啦!
测试:
var test = new MyPromise(function(resolve){
setTimeout(()=>{
resolve("测试");
},1000);
});
test.then((data)=>{
console.log(data);
return "测试2"
}).then((data)=>{
console.log(data);
});
测试结果:

情况二:当x为MyPromise
从代码上看,当x为MyPromise的时候,直接把B的resolve当做了x的注册方法;这样的话,只有当x的resolve执行的时候,会触发x用then注册的方法,才会触发B的resolve,才会触发B用then注册的方法;
流程就是:A的resolve --> A用then注册的方法 --> x的resolve --> x用then注册的方法 --> B的resolve --> B用then注册的方法,这样就实现了异步链式传递;
测试:
var test = new MyPromise(function(resolve){
setTimeout(()=>{
resolve("测试");
},1000);
});
test.then((data)=>{
console.log(data);
return new MyPromise(function(resolve){
setTimeout(()=>{
resolve("测试2");
},1000)
})
}).then((data)=>{
console.log(data);
});
测试结果:

针对状态改变的优化
以上说到的都是正常的状态下;就是说,当你用then方法注册函数的时候,都是在pending状态;但是,有的特殊场景,在你用then方法的时候,状态已经是fulfilled,就像下面这样:
var test = new MyPromise(function(resolve,reject){
setTimeout(function(){
resolve("test")
},1000);
});
setTimeout(function(){
test.then(function(data){
console.log(data);
});
},2000)
在这种情况下,当调用resolve方法的时候,并没有用then方法注册函数,那么resolveArr必然是一个空数组;此时状态是fulfilled,不可能说你用then方法注册的时候,再给你执行一边,也违背了Promise的标准。
优化then方法
MyPromise.prototype.then = function(onResolved,onRejected){
var self = this;
if(self.status==="fulfilled"){
return new MyPromise(function(resolve,reject){
var x = onResolved(self.data);
if(x instanceof MyPromise){
x.then(resolve,reject)
}else{
resolve(x);
}
});
}
if(self.status==="pending"){
return new MyPromise(function(resolve,reject){
self.resolveArr.push(function(){
var x = onResolved(self.data);
if(x instanceof MyPromise){
x.then(resolve,reject)
}else{
resolve(x);
}
});
});
}
}
在then方法中做了一层判断,当状态是fulfilled的时候,不会执行resolve,当然也不会触发then注册的函数,这里的做法是跳过这一层,直接去执行onResolved,然后继续走下去;上面原理都理解的话,这里理解起来应该也不是特别困难,这里就不做过多解释了,实在理不清楚的话就留言给我吧!
原理到这里基本上就差不多了,这里再补充一个promise静态方法
promise静态方法
Promise中有许多静态方法,像Promise.all,Promise.race,那我们用以上自己实现的MyPromise来加一个静态方法试试:
MyPromise.all = function(promiseArr){
return new MyPromise(function(resolve){
var length = promiseArr.length;
var resultIndex = 0;
var resultArr = [];
promiseArr.forEach((promise)=>{
promise.then(function(data){
resultArr.push(data);
resultIndex++;
if(resultIndex===length){
resolve(resultArr);
}
});
});
});
}
原理理解的话,这个实现起来也就不难了,这里就不讲解了,直接来测试一下吧:
var test1 = new MyPromise(function(resolve){
setTimeout(function(){
resolve("test1")
},1000)
});
var test2 = new MyPromise(function(resolve){
setTimeout(function(){
resolve("test2")
},1500)
});
var test3 = new MyPromise(function(resolve){
setTimeout(function(){
resolve("test3")
},1200)
});
结果如下:

总结
关于promise的内容到这里就差不多了,平时也比较少发表文章,总怕自己总结的不好,希望能给大家带来帮助吧!
