根据 Promises/A+ 手写 Promsie

104 阅读10分钟

在这篇文章中,我们将根据 Promises/A+ 规范试着自己来写一个Promise,主要是学习Promise的内部机制与它的编程思想。
Promise到底是什么?Promise到底是啥玩意呢?是一个类、数组、对象、函数?好了不猜了,打印出来看看吧console.dir(Promise),

是骡子是马拉出来遛遛呗!



呀呀呀!原来 Promise 是一个构造函数,自己身上有 all、resolve、reject,原型上有 then、catch 等眼熟的方法。那就不用废话了 new 一个玩玩呗!
[JavaScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
let p=new Promise((resolve,reject)=>{
console.log(1);
resolve('成功');
reject('失败');
});
console.log(2);
p.then((data)=>{
console.log(data+1);
},(err)=>{
console.log(err);
})
p.then((data)=>{
console.log(data+2);
},(err)=>{
console.log(err);
})


输出 1 2 成功1 成功2

new Promise时传递一个函数(executor执行器是立即执行的),并接收两个参数:resolve,reject,分别表示成功的回调函数与失败的回调函数。每一个实例都有一个 then 方法,参数是成功和失败,成功会有成功的值,失败会有失败的原因,并且成功就不能失败反之也一样。同一个 Promise 可以多次 then……貌似跑偏了呀!你们都懂也应该会用,还是回到主题开始自己实现吧!
1、实现基本的 Promise

[JavaScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Promise{
constructor(executor){
this.status='pending'; //默认的状态
this.value=undefined; //默认成功的值
this.reason=undefined; //默认失败的原因
this.onResolvedCallbacks=[];//存放成功的数组
this.onRejectedCallbacks=[];//存放失败的数组
let resolve=(value)=>{
if(this.status==='pending'){//这里的判 断是为了防止executor中调用两次resovle或reject方法
this.status='resolved';//成功了
this.value=value;//成功的值
}
}
let reject=(reason)=>{
if(this.status==='pending'){
this.status='rejected';//失败了
this.reason=reason;//失败的原因
}
}
try {//捕获异常
executor(resolve,reject);//默认让执行器执行
} catch (err) {
reject(err)
}
}
then(onFufilled,onRejected){
if(this.status==='resolved'){
onFufilled(this.value);
}
if(this.status==='rejected'){
onRejected(this.reason);
}
}
}
let p=new Promise((resolve,reject)=>{
resolve('成功');
//reject('失败');
})
p.then((data)=>{
console.log(data);
},(err)=>{
console.log(err);
})


  • executor:实例化 Promise 对象时传入的参数,即 (resolve,reject)=>{ }
  • status:Promise的状态,默认为 pendding 态,每当调用 resolve 或 reject 方法时,就会改变值,在后面的 then 方法中会用到
  • value:resolve回调成功的值
  • reason:reject回调成功的值
  • resolve:成功执行的函数,执行时传入的参数会作为 then 方法中第一个回调函数的参数
  • reject:失败执行的函数,执行时传入的参数会作为 then 方法中第二个回调函数的参数
执行输出成功,那么问题来了,我们费劲半天写 Promise 上来就执行有啥用?使用 Promise 时我们一般会写一些异步代码,等到异步操作执行完才会触发 resolve 或者 reject 函数。可是现在当执行 then 方法的时候此时的状态还是初始的pending 状态,所以为了能取到参数,我们可以通过发布订阅模式来实现。

2、异步处理then 方法里添加如下代码,当状态为 pending 时,我们先把回调函数存到对应的数组里,等待调用。

[JavaScript]
纯文本查看
复制代码
1
2
3
4
5
6
7
8
if(this.status==='pending'){
this.onResolvedCallbacks.push(()=>{
onFufilled(this.value);
});
this.onRejectedCallbacks.push(()=>{
onRejected(this.reason);
})
}


resolve 和 reject 方法里分别添加如下代码,当调用的时候,把对应数组里的函数依次执行

[JavaScript]
纯文本查看
复制代码
1
2
3
this.onResolvedCallbacks.forEach(fn=>fn());
this.onRejectedCallbacks.forEach(fn=>fn());


我们都知道 Promise 有一个最为重要的 then 方法。为了保证链式调用, then 方法调用后返回一个新的 Promise,会将这个 Promise 的值传递给下一次 then 中。并且上一次 then 中不管是成功还是失败或是返回一个普通值,都会传递到下一次 then 的参数。

3、实现链式 then

首先我们知道,then 是有返回值的。而且可以一直 then 下去,所以之前的 then 必须返回一个新的 Promise。所以我们根据Promises/A+对 then 方法改造如下。
[JavaScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
then(onFufilled,onRejected){
let promise2;
if(this.status==='resolved'){
promise2=new Promise((resolve,reject)=>{
let x=onFufilled(this.value);
//判断p是不是一个promise,如果是取它的结果作为promise2成功的结果,
//如果返回一个普通值,同样作为promise2成功的结果
resolvePromise(promise2,x,resolve,reject);//解析p和promise2之间的关系
});
}
if(this.status==='rejected'){
promise2=new Promise((resolve,reject)=>{
let x=onRejected(this.reason);
resolvePromise(promise2,x,resolve,reject);
})
}
if(this.status==='pending'){//当前既没有成功,也没有失败
promise2=new Promise((resolve,reject)=>{
this.onResolvedCallbacks.push(()=>{//存放成功的回调
let x=onFufilled(this.value);
resolvePromise(promise2,x,resolve,reject);
}
);
this.onRejectedCallbacks.push(()=>{//存放失败的回调
let x=onRejected(this.reason);
resolvePromise(promise2,x,resolve,reject);
})
})
}
return promise2;//调用then后返回一个新的promise
}


resolvePromise 是干啥的呢?由于 then 可能返回任意值,所以根据Promises/A+规范对 then 返回的值进行如下处理或解析。
[JavaScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function resolvePromise(promise2,x,resolve,reject){
//判断x是不是promise
//如果当前返回的promise和x引用同一个对象报类型错误(不能自己等待自己完成)
if(promise2===x){
return reject(new TypeError('循环引用'));
}
//x不是null并且是对象或函数时,可能是promise
if(x!==null&&(typeof x==='object'|| typeof x==='function')){
let called; //标识当前promise有没有调用过
try{//尽量让别人瞎写,防止取then时出现异常
let then=x.then;//取x的then看是不是函数
if(typeof then==='function'){//如果是函数就认为它是promise
then.call(x,(y)=>{//第一个参数是this,后面的是成功的回调和失败的回调
if(called) return;
called=true;
resolvePromise(promise2,y,resolve,reject);//如果y是promise继续递归解析
},(err)=>{//只要有一个失败了就失败了
if(called) return;
called=true;
reject(err);
})
}else{//then是一个普通对象直接成功
resolve(x);
}
}catch(e){
if(called) return;
called=true;
reject(e);
}
}else{//如果x是普通值直接成功
resolve(x);
}
}


值的穿透我们用 Promise 时发现,当不给 then 中传入参数时,后面的 then 依旧可以得到之前 then 的返回值。例如:p.then().then(),这就是值的穿透。

[JavaScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
then(onFufilled,onRejected){
//解决onFufilled或onRejected没有传的问题
onFufilled=typeof onFufilled==='function'?onFufilled:d=>d;
onRejected=typeof onRejected==='function'?onRejected:e=>{throw e};
let promise2;
if(this.status==='resolved'){
promise2=new Promise((resolve,reject)=>{
let x=onFufilled(this.value);
//判断p是不是一个promise,如果是取它的结果作为promise2成功的结果,
//如果返回一个普通值,同样作为promise2成功的结果
resolvePromise(promise2,x,resolve,reject);//解析p和promise2之间的关系
});
}
if(this.status==='rejected'){
promise2=new Promise((resolve,reject)=>{
let x=onRejected(this.reason);
resolvePromise(promise2,x,resolve,reject);
})
}
if(this.status==='pending'){//当前既没有成功,也没有失败
promise2=new Promise((resolve,reject)=>{
this.onResolvedCallbacks.push(()=>{//存放成功的回调
let x=onFufilled(this.value);
resolvePromise(promise2,x,resolve,reject);
}
);
this.onRejectedCallbacks.push(()=>{//存放失败的回调
try{
let x=onRejected(this.reason);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
})
})
}
return promise2;//调用then后返回一个新的promise
}


executor 执行的时候我们在外面包了 try{}catech 但是我们内部代码是异步的,就无法捕获错误了,需要给每个 then 中的方法都加一个 try{}catch
[JavaScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
then(onFufilled,onRejected){
//解决onFufilled或onRejected没有传的问题
onFufilled=typeof onFufilled==='function'?onFufilled:d=>d;
onRejected=typeof onRejected==='function'?onRejected:e=>{throw e};
let promise2;
if(this.status==='resolved'){
promise2=new Promise((resolve,reject)=>{
setTimeout(() => {
try{
let x=onFufilled(this.value);
//判断p是不是一个promise,如果是取它的结果作为promise2成功的结果,
//如果返回一个普通值,同样作为promise2成功的结果
resolvePromise(promise2,x,resolve,reject);//解析p和promise2之间的关系
}catch(e){
reject(e);
}
}, 0);
});
// return promise2;
}
if(this.status==='rejected'){
promise2=new Promise((resolve,reject)=>{
setTimeout(() => {
try{
let x=onRejected(this.reason);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
}, 0);
})
// return promise2;
}
if(this.status==='pending'){//当前既没有成功,也没有失败
promise2=new Promise((resolve,reject)=>{
this.onResolvedCallbacks.push(()=>{//存放成功的回调
setTimeout(() => {
try{
let x=onFufilled(this.value);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(()=>{//存放失败的回调
setTimeout(() => {
try{
let x=onRejected(this.reason);
resolvePromise(promise2,x,resolve,reject);
}catch(e){
reject(e);
}
}, 0);
})
})
// return promise2;
}
return promise2;//调用then后返回一个新的promise
}


4、Promise 其他方法实现4.1 catch

catch 接收的参数只有错误,也就相当于 then 方法没有成功的简写。而且 catch 后依然可以 then,那就简单暴力上代码吧!
[JavaScript]
纯文本查看
复制代码
1
2
3
catch(onRejected){
return this.then(null,onRejected);
}


4.2 resolve与reject

Promise.resolve()、Promise.reject() 这两种用法,是直接可以通过类调用的,原理就是返回一个内部是resolve 或 reject 的 Promise 对象。
[JavaScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
Promise.resolve=function(val){
return new Promise((resolve,reject)=>{
resolve(val)
})
}
Promise.reject=function(val){
return new Promise((resolve,reject)=>{
reject(val)
})
}


4.3 all

all方法的作用就是将一个数组的 Promise 对象放在其中,当全部 resolve 的时候就会执行 then 方法,当有一个 reject 的时候就会执行 catch,并且他们的结果也是按着数组中的顺序来的.
[JavaScript]
纯文本查看
复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
Promise.all = function(promises){
let arr = [];
let i = 0;
function processData(index,data){
arr[index] = data;
i++;
if(i == promises.length){
resolve(arr);
}
}
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(data=>{
processData(i,data);
},reject)
}
})
}


5、测试

写了这么多,到底符不符合Promises/A+规范呢?
[JavaScript]
纯文本查看
复制代码
1
2
3
4
5
6
7
8
9
//promise的语法糖
Promise.defer=Promise.deferred=function(){
let dfd={};
dfd.promise=new Promise((resolve,reject)=>{
dfd.resolve=resolve;
dfd.reject=reject;
})
return dfd;
}


安装promises-aplus-tests用来测试,安装:npm install promises-aplus-tests -g,测试:promises-aplus-tests + "文件名"。

到这基本就简单实现了一个自己的 Promise,此时对 Promise 的内部机制与它的编程思想有没有更深入的理解呢?新手可能一脸懵逼,大牛可能一脸蔑视。希望大家都有收获。写的不好,有问题欢迎大家在评论区评论指正(还不快去点赞⊙﹏⊙)!!!
代码地址


文章转载自:juejin.cn/post/684490…