本文想从一个全新的角度来理解Promise的实现原理,即通过Promise的一些外在表现,一步步的去实现一个符合Promise所有行为的Promisee。
虽然说是针对小白用户的,但是如果对Promise的使用一无所知的话,恕老夫也无能为力了,还是先去学习一下Promise的用法吧。
下面我们根据promise的一些测试用例慢慢完善我们自己的Promise,希望读者能打开编辑器,跟着本文的步骤一起去敲一下代码。看看为了实现Promise的每个行为都做了哪些工作,这样到最后Promise内部的运转机制就尽在我们掌握了。我们姑且把咱们要实现的Promise叫做MyPromise吧。
首先我们新建一个MyPromise构造函数.一开始MyPromise是一个空函数。
function MyPromise(fn){
}
这里fn是在创建Promise实例时使用者传给Promise的回调函数,我们知道fn里会有两个参数resolve和reject。
0、测试fn同步执行
我们知道Promise接收的回调fn是同步执行的,以下代码会先打印1,再打印2
//0 测试fn同步执行
var a = new Promise((resolve, reject) => {
console.log(1)
});
console.log(2)
那么我们的MyPromise改为
function MyPromise(fn){
fn()
}
这样我们的MyPromise就具备了promise同步执行的行为。
刚才说了fn接收resolve和reject两个参数,我们就在MyPromise内部定义两个函数分别叫resolve和reject,而且把这俩当参数传给fn调用。
Promise的实例有一个then参数,我们一块给定义了。
function MyPromise(fn){
function resolve(){
}
function reject(){
}
fn(resolve,reject);
this.then=function(onFullfilled,onRejected){}
}
1、测试resolve,reject,then
在resolve时会调用传给then的第一个函数,即成功回调,并把resolve的值传给成功回调。
在reject时会调用传给then的第二个函数,即失败回调,并把reject的值传给失败回调。
//1 测试resolve
var a = new Promise((resolve, reject) => {
resolve('success');
});
a.then((val) => {
console.log(val) //打印success
})
//2 测试reject
var b = new Promise((resolve, reject) => {
reject('error');
});
b.then((val) => {
console.log(val)
}, (error) => {
console.log(error) //打印error
})
我们定义一个数组deffers来保存then函数传入的成功失败回调,并在resolve或者reject的时候有选择性的执行其中一个。 这里还声明了value和status两个变量,value代表当前Promise最终改变的值,是resolve的值或者reject的error。status代表Promise的状态,最初是pending,用null表示。reoslve了改变为true,reject的话改为false。只能改变一次,无论转成true还是false都不能再改变了。
function MyPromise(fn){
var value; //resolve或者reject的值,在resolve或者reject时改变
var status=null; //该Promise的状态,null:初始, true:成功(resolve), false:失败(reject)
var deffers=[] //回调数组,每调用一次then,
//就往里push一个{onFullFilled:onFullFilled,onRejected:onRejected}的回调对。
//并在resolve或者reject时遍历调用每一个onFullFilled或者onRejected函数
fn(resolve,reject);
function resolve(val){
value=val;
status=true;
final()
}
function reject(val){
value=val;
status=false;
final()
}
//遍历执行deffers里的函数
function final(){
for(var i=0,len=deffers.length;i<len;i++){
handle(deffers[i]) //deffers[i]=>{onFullFilled:onFullFilled,onRejected:onRejected}
}
}
//真正的处理结果的函数,根据status的值来判断
function handle(deffer){ //deffer=>{onFullfilled:onFullfilled,onRejected:onRejected}
if(status===true){
deffer.onFullfilled && deffer.onFullfilled(value)
}
if(status===false){
deffer.onRejected && deffer.onRejected(value)
}
}
this.then=function(onFullfilled,onRejected){
var deffer = {onFullfilled:onFullfilled,onRejected:onRejected}
deffers.push(deffer);
handle(deffer);
}
}
2 测试异步的resolve和异步的reject
//3 测试异步的resolve
var b = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success');
}, 1000);
});
b.then((val) => {
console.log(val, ' async') //1s后打印 success async
}, (error) => {
console.log(error, ' async')
})
//4 测试异步的reject
var b = new Promise((resolve, reject) => {
setTimeout(() => {
reject('error');
}, 1000);
});
b.then((val) => {
console.log(val, ' async')
}, (error) => {
console.log(error, ' async') //1s后打印 error async
})
这部分的代码逻辑上一步已经实现了,不再赘述
3 测试resolve和reject都调用的情况
即使一个Promise的resolve和reject都调用,Promise的状态会根据调用顺序只改变一次,后边的resolve或者reject是无效的。
//5 测试resolve 和 reject都会调用的情况 先调用resolve
var b = new Promise((resolve, reject) => {
resolve("success");
reject('error');
});
b.then((val) => {
console.log(val, '同时调用') //打印 success 同时调用
}, (error) => {
console.log(error, '同时调用')
})
//5 测试resolve 和 reject都会调用的情况 先调用reject
var b = new Promise((resolve, reject) => {
setTimeout(()=>{
resolve("success");
},100)
reject('error');
});
b.then((val) => {
console.log(val, '同时调用')
}, (error) => {
console.log(error, '同时调用') //打印 error 同时调用
})
我们的MyPromise实现如下,只贴修改的部分
function MyPromise(fn){
...
//fn(resolve,reject);
doResolve(fn,resolve,reject) //不调用fn,改为调用doResolve,doResolve里调用fn
function doResolve(fn,resolve,reject){
var hasChange =false;
fn((value)=>{
if(hasChange){
return;
}
hasChange=true; //开关,调用一次resolve或者reject之后就设置为true,
//下次就不会进来啦。下面同理
resolve(value)
},(error)=>{
if(hasChange){
return;
}
hasChange=true;
reject(value)
})
}
}
4 测试同一个promise调用多个then
Promise可以调用多次then,就像上文所说,这些回调都被放入了Promise内的deffers数组,并在resolve或者reject时遍历deffers依次调用,这部分的逻辑上文已经完成。
//6 测试同一个promise调用多个then
var b = new Promise((resolve, reject) => {
resolve('success')
});
b.then((val) => {
console.log(val, ' then1')
}, (error) => {
console.log(error, ' then1')
})
b.then((val) => {
console.log(val, ' then2')
}, (error) => {
console.log(error, ' then2')
})
5 测试连续调用then的情况
Promise调用then方法的返回值是一个新的Promise,这个新Promise会接收前一个Promise的then回调里的成功回调返回值调用resolve,或者失败回调返回值调用reject,因此
var b = new Promise((resolve, reject) => {
resolve('success')
});
var newP = b
.then((val) => {
console.log(val, ' then1');
return '进入第二个then的成功回调' //如果b被resolve了,newP就会resolve 这个回调的返回值
}, (error) => {
console.log(error, ' then1');
return '进入第二个then的失败回调' //如果b被reject了,newP就会reject 这个回调的返回值
});
newP.then((val) => {
console.log(val, ' then2');
}, (error) => {
console.log(error, ' then2');
})
这里我们需要修改一下then方法
function MyPromise(fn){
...
this.then=function(onFullfilled,onRejected){
return new MyPromise((resolve,reject)=>{
let deffer = {onFullfilled:onFullfilled,onRejected:onRejected,resolve:resolve,reject:reject};
deffers.push(deffer)
handle(deffer)
})
}
}
这里有几处修改,第一点是让then方法返回一个新的MyPromise,第二个是deffers数组里的每一个对象都缓存了新MyPromise的resolve和reject,这样前一个MyPromise的成功回调执行完了我们可以取出新MyPromise的resolve执行,这样就会形成一个管道了。reject同理。
下面我们来处理MyPromise链式调用的逻辑,主要集中在handle函数里
function MyPromise(fn){
...
//真正的处理结果的函数,根据status的值来判断
function handle(deffer){ //deffer=>{onFullfilled:onFullfilled,onRejected:onRejected,resolve:resolve,reject:reject} 这里的deffers缓存了新Promise的resolve和reject
//cb是处理函数,根据status来取
var cb = status===true?deffer.onFullfilled : deffer.onRejected;
//如果没有传处理函数,比如resolve却没有传成功回调,我们就执行新Promise的resolve,把成功结果往后传递.
if(cb==null){
status==true? deffer.resolve(value):deffer.reject(value);
return;
}
//下面是有cb的逻辑
var val;
//这里用try catch的原因是成功和失败回调是使用者定义的,执行可能会报错,一旦报错就进入下个Promise的reject流程
try{
val=cb(value);
}catch(e){
deffer.reject(e);
return ;
}
//这里只要成功拿到val值,不论cb是当前Promise的成功处理函数,还是失败处理函数,只要当前Promise处理了,就进入下个Promise的resolve流程
deffer.resolve(val)
}
}
到这里,结合then方法和handle方法,MyPromise可以链式调用的逻辑就处理完了。handle里处理了then调用时不传回调的情况,也处理了传的回调函数执行报错的情况。所以以下测试是通过的
// 连续调用then
var b = new MyPromise((resolve, reject) => {
resolve('success')
});
b.then(value=>{
console.log(value,' then1 连续调用then'); //这里会打印,value='success'
return 'new data'
}, (error) => {
console.log(error, ' then1')
}).then(value=>{
console.log(value) // 这里会打印,value='new data'
})
// 测试then里不写某个回调
var b = new MyPromise((resolve, reject) => {
resolve('success')
});
b.then(null, (error) => {
console.log(error, ' then1')
}).then((val) => {
console.log(val, ' then2 then1没有成功回调') //这里会打印,因为前一个MyPromise没有传成功回调,就会进入这里
}, (error) => {
console.log(error, ' then2 then1没有成功回调')
});
var b = new MyPromise((resolve, reject) => {
reject('error')
});
b.then((value) => {
console.log(value, ' then1')
}, null).then((val) => {
console.log(val, ' then2 then1没有失败回调')
}, (error) => {
console.log(error, ' then2 then1没有失败回调') //这里会打印,因为前一个MyPromise没有传失败回调,就会进入这里
});
//测试回调里报错
var b = new MyPromise((resolve, reject) => {
resolve('error')
});
b.then((value) => {
throw "抛个错误";
return 333;
}).then((val) => {
console.log(val, ' then2 then1执行报错')
}, (error) => {
console.log(error, ' then2 then1执行报错') //这里会打印,因为then1里的成功回调执行时报错了
});
到这里我们关于Promise的逻辑基本就完成了。等等,还有种情况,有时我们会resolve一个新的Promise,或者在then的回调里又返回一个新的Promise,这个该怎么解决呢?
6 测试resolve一个Promise或者then的回调返回新Promise的情况
下面我们就来处理这个问题,这部分逻辑主要在MyPromise内部定义的resolve方法里,这里的resolve的参数value有可能是个新的Promise
function MyPromise(fn){
...
function resolve(val){
//这里用鸭式判别法来断定val是个Promise对象,只要有then方法就可以了
if (val && (typeof val === "object" || typeof val === "function")) {
var then = val.then;
if (typeof then === "function") {
//如果val是个Promise对象,就把当前Promise的resolve和reject当做回调传给val的then,
//等val本身状态变更时自会调用resolve和reject,这样就能拿到val最终要传给我们的值啦
doResolve(then.bind(val), resolve, reject);
return;
}
}
//走到这里说明val已经是个非Promise对象了
value=val;
status=true;
final()
}
}
以上就是对Promise对象的解析,当然还有一些细节还需要完善。如果能看到这里并且看明白了,以后再用Promise就可以做到知其然并知其所以然了。