实现Promise/A+规范的Promise

Promise是ES6的核心内容,有效解决回调地狱的问题,我们在此解读Promise/A+规范,并手写实现一个Promsie。

文章内容主要来源于实现一个完美符合Promise/A+规范的Promise

个人博客主要作为笔记记录使用。如不允许转载,烦请联系删除。感谢。

解读一下Promise/A+规范

术语

  • “promise”是一个对象或者函数,该对象或者函数有一个then方法

  • “thenable”是一个对象或者函数,用来定义then方法

  • “value”是promise状态成功时的值

  • “reason”是promise状态失败时的值

要求

  • 一个promise必须有3个状态,pending,fulfilled(resolved),rejected当处于pending状态的时候,可以转移到fulfilled(resolved)或者rejected状态。当处于fulfilled(resolved)状态或者rejected状态的时候,就不可变。

  • promise英文译为承诺,也就是说promise的状态一旦发生改变,就永远是不可逆的。

  • 一个promise必须有一个then方法,then方法接受两个参数:

1
2
// 其中onFulfilled方法表示状态从pending——>fulfilled(resolved)时所执行的方法,而onRejected表示状态从pending——>rejected所执行的方法。
promise.then(onFulfilled,onRejected)
  • 为了实现链式调用,then方法必须返回一个promise
1
promise2=promise1.then(onFulfilled,onRejected)

初始版本的Promise

一个简单版本的Promise,注意其尚不能处理异步任务,.then也不能返回Promise

1
2
3
4
5
6
7
8
9
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
// myPromise构造函数
function myPromise(constructor){
let self = this
// 初始状态为pending
self.status = "pending"
// 状态为fullFilled状态的值
self.value = undefined
// 状态为rejected状态的值
self.reason = undefined
// resolve方法,接收value
function resolve(value){
if(self.status === "pending"){
self.value = value
self.status = "fullFilled"
}
}
// reject方法,接收reason
function reject(reason){
if(self.status === "pending"){
self.reason = reason
self.status = "rejected"
}
}

// 调用constructor并捕获异常
// 我们调用new Promise的时候传入一个函数,函数接收resolve和reject两个参数,所以在这里我们调用传入的constructor并传入resolve和reject
try{
constructor(resolve, reject)
}catch(e){
reject(e)
}
}
// 在原型添加.then方法
// .then方法接收两个函数,分别是resolve的回调和reject的回调
myPromise.prototype.then = function(onFullfilled, onRejected){
let self = this
switch(self.status){
case "fullFilled":
// 调用resolve的回调并传入value
onFullfilled(self.value)
break;
case "rejected":
// 调用reject的回调并传入reason
onRejected(self.reason)
break;
default:
break;
}
}

这里myPromise还无法处理异步的resolve

比如:

1
2
3
4
5
6
7
var p=new myPromise(function(resolve,reject){
setTimeout(function(){
resolve(1)
},1000)
});
p.then(function(x){console.log(x)})
//无输出

基于观察者模式处理异步

用2个数组onFullfilledArray和onRejectedArray来保存异步的方法。在状态发生改变时,一次遍历执行数组中的方法。

基本逻辑就是:

  • 在.then执行的时候,如果状态还是pending,那么就把回调函数收集到数组中
  • 在执行resolve或者reject的时候,执行数组中保存的方法
1
2
3
4
5
6
7
8
9
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
62
63
64
65
66
67
68
69
70
71
// myPromise构造函数
function myPromise(constructor){
let self = this
// 初始状态为pending
self.status = "pending"
// 状态为fullFilled状态的值
self.value = undefined
// 状态为rejected状态的值
self.reason = undefined
// 保存resolve的回调函数列表
self.onFullFilledArray = []
// 保存reject的回调函数列表
self.onRejectedArray = []

// resolve方法,接收value
function resolve(value){
if(self.status === "pending"){
self.value = value
self.status = "fullFilled"
// 调用resolve的时候执行resolve的所有回调
self.onFullFilledArray.forEach( fn => {
fn(self.value)
})
}
}
// reject方法,接收reason
function reject(reason){
if(self.status === "pending"){
self.reason = reason
self.status = "rejected"
// 调用reject的时候执行reject的所有回调
self.onRejectedArray.forEach(fn => {
fn(self.reason)
})
}
}

// 调用constructor并捕获异常
// 我们调用new Promise的时候传入一个函数,函数接收resolve和reject两个参数,所以在这里我们调用传入的constructor并传入resolve和reject
try{
constructor(resolve, reject)
}catch(e){
reject(e)
}
}
// 在原型添加.then方法
// .then方法接收两个函数,分别是resolve的回调和reject的回调
myPromise.prototype.then = function(onFullFilled, onRejected){
let self = this
switch(self.status){
// 如果调用.then的时候是pending状态,那么可能是一个异步任务,还没有执行resolve/reject,所以先把对应的回调函数收集起来,等待时机运行
case "pending":
self.onFullFilledArray.push(function(value){
onFullFilled(value)
})
self.onRejectedArray.push(function(reason){
onRejected(reason)
})
break;
case "fullFilled":
// 调用resolve的回调并传入value
onFullFilled(self.value)
break;
case "rejected":
// 调用reject的回调并传入reason
onRejected(self.reason)
break;
default:
break;
}
}

经过这样改造,再看上边的异步任务的例子:

  • 开始new Promise中执行到了定时器会将其加入到宏任务队列,并不会立即执行定时器中的内容
  • 紧接着执行.then方法,此时由于定时器中的内容还未执行,所以status为pending,我们将onFullFilled和onRejected方法收集起来
  • 执行完之后会执行定时器也就会调用resolve方法,此时改变status的状态,并通知收集的依赖进行更新执行相应的回调。

异步的问题解决了,但是Promise/A+规范的最大的特点就是链式调用,也就是说then方法返回的应该是一个promise。

then方法实现链式调用

要通过then方法实现链式调用,那么也就是说then方法每次调用需要返回一个primise,同时在返回promise的构造体里面,增加错误处理部分

由于只需要对then方法进行修改,下边就不展示构造函数的内容了,只放then方法上来

1
2
3
4
5
6
7
8
9
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
myPromise.prototype.then = function(onFullFilled, onRejected){
let self = this
// 要返回的新promise
let promise2;
switch(self.status){
case "pending":
promise2 = new myPromise(function(resolve, reject){
self.onFullFilledArray.push(function(value){
try{
let temp = onFullFilled(value)
resolve(temp)
}catch(e){
reject(e)
}
})
self.onRejectedArray.push(function(reason){
try{
let temp = onRejected(reason)
resolve(temp)
}catch(e){
reject(e)
}
})
})
break;
case "fullFilled":
promise2 = new myPromise(function(resolve, reject){
try{
let temp = onFullFilled(self.value)
resolve(temp)
}catch(e){
reject(e)
}
})
break;
case "rejected":
promise2 = new myPromise(function(resolve, reject){
try{
let temp = onRejected(self.reason)
resolve(temp)
}catch(e){
reject(e)
}
})
break;
default:
break;
}
// 返回新的promise
return promise2
}

由于掺杂了异步的逻辑可能有些乱,可以只看状态为fullFilled和rejected的情况会清晰一些,然后加上异步的处理就可以理解了。

基本流程就是我们会在.then方法中创建一个新的promsie返回来实现.then的链式调用。

在这个新的promise中,我们要执行之前的promise.then的回调函数,并根据其回调函数的返回值在新的回调函数中执行resolve/reject操作,以保证在下一个.then方法中可以接收到我们传递的值。

还有一个问题,就是在Promise/A+规范中then函数里面的onFullfilled方法和onRejected方法的返回值可以是对象,函数,甚至是另一个promise。

解决then函数中的onFullfilled和onRejected方法的返回值问题

特别的为了解决onFullfilled和onRejected方法的返回值可能是一个promise的问题。

首先来看promise中对于onFullfilled函数的返回值的要求

  • 如果onFullfilled函数返回的是该promise本身,那么会抛出类型错误

  • 如果onFullfilled函数返回的是一个不同的promise,那么执行该promise的then函数,在then函数里将这个promise的状态转移给新的promise

  • 如果返回的是一个嵌套类型的promsie,那么需要递归。

  • 如果返回的是非promsie的对象或者函数,那么会选择直接将该对象或者函数,给新的promise。

根据上述返回值的要求,我们要重新的定义resolve函数,这里Promise/A+规范里面称为:resolvePromise函数,该函数接受当前的promise、onFullfilled函数或者onRejected函数的返回值、resolve和reject作为参数。

1
2
3
4
5
6
7
8
9
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// myPromise构造函数
function myPromise(constructor){
let self = this
// 初始状态为pending
self.status = "pending"
// 状态为fullFilled状态的值
self.value = undefined
// 状态为rejected状态的值
self.reason = undefined
// 保存resolve的回调函数列表
self.onFullFilledArray = []
// 保存reject的回调函数列表
self.onRejectedArray = []

// resolve方法,接收value
function resolve(value){
if(self.status === "pending"){
self.value = value
self.status = "fullFilled"
// 调用resolve的时候执行resolve的所有回调
self.onFullFilledArray.forEach( fn => {
fn(self.value)
})
}
}
// reject方法,接收reason
function reject(reason){
if(self.status === "pending"){
self.reason = reason
self.status = "rejected"
// 调用reject的时候执行reject的所有回调
self.onRejectedArray.forEach(fn => {
fn(self.reason)
})
}
}

// 调用constructor并捕获异常
// 我们调用new Promise的时候传入一个函数,函数接收resolve和reject两个参数,所以在这里我们调用传入的constructor并传入resolve和reject
try{
constructor(resolve, reject)
}catch(e){
reject(e)
}
}
// 在原型添加.then方法
// .then方法接收两个函数,分别是resolve的回调和reject的回调
myPromise.prototype.then = function(onFullFilled, onRejected){
let self = this
// 要返回的新promise
let promise2;
switch(self.status){
case "pending":
promise2 = new myPromise(function(resolve, reject){
self.onFullFilledArray.push(function(value){
setTimeout(function(){
try{
let temp = onFullFilled(value)
resolvePromise(promise2, temp, resolve, reject)
}catch(e){
reject(e)
}
})
})
self.onRejectedArray.push(function(reason){
setTimeout(function(){
try{
let temp = onRejected(reason)
resolvePromise(promise2 ,temp, resolve, reject)
}catch(e){
reject(e)
}
})
})
})
break;
case "fullFilled":
promise2 = new myPromise(function(resolve, reject){
setTimeout(function(){
try{
let temp = onFullFilled(self.value)
resolvePromise(promise2 ,temp, resolve, reject)
}catch(e){
reject(e)
}
})
})
break;
case "rejected":
promise2 = new myPromise(function(resolve, reject){
setTimeout(function(){
try{
let temp = onRejected(self.reason)
resolvePromise(promise2 ,temp, resolve, reject)
}catch(e){
reject(e)
}
})
})
break;
default:
break;
}
// 返回新的promise
return promise2
}
/**
* @desc 处理返回值
* @param {*} promise .then中要返回的那个新的promise
* @param {*} x .then中的onFullFilled/onRejected回调函数的返回值
* @param {*} resolve 新promise的resolve
* @param {*} reject 新promise的reject
* @returns
*/
function resolvePromise(promise, x, resolve,reject){
// 如果返回的promise本身则报错
if(promise === x){
throw new Error("type error")
}
let isUsed;
if(x!==null && (typeof x==="object" || typeof x === "function")){
try{
let then = x.then
// 如果x.then是一个函数,那么x就是一个promise
if(typeof then === "function"){
then.call(x, function(y){
if(isUsed){
return;
}
isUsed = true
// onFullFilled回调可能还会返回promise
resolvePromise(promise, y, resolve, reject)
}, function(e){
if(isUsed){
return;
}
isUsed = true
reject(e)
})
}else{
// 如果x没有then方法那么仅仅是一个普通函数或者对象
resolve(x)
}
}catch(e){
if(isUsed){
return;
}
isUsed = true
reject(e)
}
}else{
// 返回的基本数据类型,直接resolve
resolve(x)
}
}
作者

胡兆磊

发布于

2022-08-03

更新于

2022-10-23

许可协议