Promise原理

整体结构搭建

首先看看Promise是怎么使用的:

1
2
3
4
5
6
7
8
let p = new Promise((resolve, reject) => {
resolve("OK");
});
p.then(res => {
console.log(res);
}, err => {
console.log(err);
})

Promise其实是一个类,它的构造函数接收一个executor,executor在Event Loop中,是属于同步任务的。executor是一个函数,它接收两个参数,一个参数是resolve,一个是reject。resolve是Promise成功的回调,reject是Promise失败的回调。

在浏览器开发者工具中,创建并打印一个Promise,可以看到一个Promise对象有两个属性:PromiseState、PromiseResult,PromiseState是Promise当前的状态,Promise有pending、rejected、fulfilled三个状态,分别代表进行中,已失败,已成功。还有三个方法:then、catch、finally,then是Promise成功时会调用的方法,catch时Promise失败时被调用的方法,finally是最后状态无论如何都会被调用的方法。

创建两个Promise,并分别调用resolve和reject,可以看到Promise的状态被改变,PromiseResult也变成了resolve/reject函数里的值。

创建Promise.js文件,根据上面的特性,可以创建Promise的整体结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Promise {
PromiseState = "pending";
PromiseResult = null;

constructor(executor) {
function resolve(value) { }

function reject(reason) { }

executor(resolve, reject);
}

then(onResolved, onRejected) {

}

catch(onRejected) {

}
}

resolve和reject的实现

在上面的例子中,可以看到resolve和reject主要实现下面几个功能:

  1. 修改Promise的状态:修改Promise对象中的PromiseState属性
  2. 修改Promise的结果:修改Promise对象中的PromiseResult属性
  3. 将结果传递给then/catch。

另外要注意的是,Promise的状态只能被修改一次,也就是说在修改状态之前,要判断Promise的状态是不是pending。

如果在executor中抛出异常,异常会被传入reject函数中,所以在调用executor时,我们要使用try…catch。

根据上面的思路,可以写出下面的代码:

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
class Promise {
PromiseState = "pending";
PromiseResult = null;

constructor(executor) {
var resolve = (value) => {
// 如果Promise状态被修改过,就不能再修改了
if (this.PromiseState != "pending") {
return;
}
this.PromiseState = "fulfilled";
this.PromiseResult = value;
}

var reject = (reason) => {
// 如果Promise状态被修改过,就不能再修改了
if (this.PromiseState != "pending") {
return;
}
this.PromiseState = "rejected";
this.PromiseResult = reason;
}

// 因为在executor中抛出异常,回调用reject,所以要用try...catch捕捉异常
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}

then(onResolved, onRejected) {

}

catch(onRejected) {

}
}

实现then方法

then的基本实现

then方法回接收两个回调函数,一个是成功回调,一个是失败回调,回调函数接受的参数,就是Promise里resolve/reject的值,也就是PromiseResult。在调用回调时,可以根据Promise的状态来判断需要调用哪一个回调函数:

1
2
3
4
5
6
7
8
then(onResolved, onRejected) {
if (this.PromiseState == "fulfilled") {
onResolved(this.PromiseResult);
}
if (this.PromiseState == "rejected") {
onRejected(this.PromiseResult);
}
}

现在新建一个html文件,引入我们自己写的Promise.js,并运行上面的示例,已经可以正常使用了。

实现异步任务执行then中的回调

但是现在,如果把Promise中的executor改成一个异步的任务:

1
2
3
4
5
6
7
8
9
10
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("OK");
}, 100)
});
p.then(res => {
console.log(res);
}, err => {
console.log(err);
})

可以发现then方法不会执行,这是因为executor中是一个异步的任务,执行的优先程度在同步代码之后,因此会先执行then方法,但是在执行then方法时,Promise的状态还是处于pending,没有被判断到,所以我们要加一个pending的判断。在判断里做什么呢?当然并不能直接调用回调函数,因为我们还不知道Promise的状态和结果;等到Promise出了结果了,而这一个then不会再被调用一次了,因此在这里,我们需要把成功和失败的回调保存起来,然后在Promise修改状态后去执行,也就是在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
class Promise {
PromiseState = "pending";
PromiseResult = null;
// 保存回调函数
callback = {};
constructor(executor) {
var resolve = (value) => {
// 如果Promise状态被修改过,就不能在修改了
if (this.PromiseState != "pending") {
return;
}
this.PromiseState = "fulfilled";
this.PromiseResult = value;
// 执行回调
if (this.callback.onResolved) {
this.callback.onResolved(value);
}
}

var reject = (reason) => {
// 如果Promise状态被修改过,就不能在修改了
if (this.PromiseState != "pending") {
return;
}
this.PromiseState = "rejected";
this.PromiseResult = reason;
// 执行回调
if (this.callback.onRejected) {
this.callback.onRejected(reason);
}
}

// 因为在executor中抛出异常,回调用reject,所以要用try...catch捕捉异常
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}

then(onResolved, onRejected) {
if (this.PromiseState == "fulfilled") {
onResolved(this.PromiseResult);
}
if (this.PromiseState == "rejected") {
onRejected(this.PromiseResult);
}
if (this.PromiseState == "pending") {
this.callback = { onResolved, onRejected };
}
}
}

实现多个then方法的调用

而这里还有一个问题,Promise支持多个then,如果把示例改成:

1
2
3
4
5
6
7
8
9
10
11
12
13
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("OK");
}, 100)
});
p.then(res => {
console.log(res);
}, err => {
console.log(err);
})
p.then(res => {
alert(res);
})

会发现浏览器只会运行alert,这是因为在第二次运行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
52
53
class Promise {
PromiseState = "pending";
PromiseResult = null;
// 保存回调函数
callbacks = [];
constructor(executor) {
var resolve = (value) => {
// 如果Promise状态被修改过,就不能在修改了
if (this.PromiseState != "pending") {
return;
}
this.PromiseState = "fulfilled";
this.PromiseResult = value;
// 执行回调
this.callbacks.forEach(item => {
item.onResolved(value);
});
}

var reject = (reason) => {
// 如果Promise状态被修改过,就不能在修改了
if (this.PromiseState != "pending") {
return;
}
this.PromiseState = "rejected";
this.PromiseResult = reason;
// 执行回调
this.callbacks.forEach(item => {
item.onRejected(reason);
});
}

// 因为在executor中抛出异常,回调用reject,所以要用try...catch捕捉异常
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}

then(onResolved, onRejected) {
if (this.PromiseState == "fulfilled") {
onResolved(this.PromiseResult);
}
if (this.PromiseState == "rejected") {
onRejected(this.PromiseResult);
}
if (this.PromiseState == "pending") {
var callback = { onResolved, onRejected };
this.callbacks.push(callback);
}
}
}

实现同步任务中then方法的返回值

then的返回值也是一个Promise,这个Promise的状态是由then决定的:如果then里调用的是成功回调,那么返回的Promise的状态是成功的;如果调用的是失败回调,那返回的Promise状态相应就是失败的。返回的Promise的结果也是由then函数的返回值决定的:

1
2
3
4
5
6
7
8
9
let p = new Promise((resolve, reject) => {
resolve("OK");
});
var res = p.then(res => {
return res;
}, err => {
return err;
})
console.log(res);

使用浏览器里的Promise的运行结果:

而使用我们自己写的Promise,只会打印一个undefined,这是因为then方法里还没有写返回值。要实现Promise的返回值,需要在then里再返回一个Promise,如果回调的返回值是一个Promise,那么返回的Promise的状态就由回调返回的Promise来决定。修改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
then(onResolved, onRejected) {
// then函数返回一个Promise
return new Promise((resolve, reject) => {
if (this.PromiseState == "fulfilled") {
// 如果在回调函数中抛出错误,返回的Promise应该是失败状态的,因此要捕获回调函数的异常,把异常传给reject
try {
// 要获取回调函数的返回值
let result = onResolved(this.PromiseResult);
// 如果返回值是一个Promise,这个then返回的Promise状态由这个Promise决定
if (result instanceof Promise) {
result.then(res => {
resolve(res);
}, err => {
reject(err);
})
} else {
resolve(result);
}
} catch (e) {
reject(e);
}
}
if (this.PromiseState == "rejected") {
// 如果在回调函数中抛出错误,返回的Promise应该是失败状态的,因此要捕获回调函数的异常,把异常传给reject
try {
// 要获取回调函数的返回值
let result = onRejected(this.PromiseResult);
// 如果返回值是一个Promise,这个then返回的Promise状态由这个Promise决定
if (result instanceof Promise) {
result.then(res => {
resolve(res);
}, err => {
reject(err);
})
} else {
resolve(result);
}
} catch (e) {
reject(e);
}
}
if (this.PromiseState == "pending") {
var callback = { onResolved, onRejected };
this.callbacks.push(callback);
}
})
}

实现异步任务中then方法的返回值

把Promise的executor改成一个异步的任务,测试一下上面的then方法:

1
2
3
4
5
6
7
8
9
let p = new Promise((resolve, reject) => {
setTimeout(() => { resolve(1) }, 100);
});
var res = p.then(res => {
return res;
}, err => {
return err;
})
console.log(res);

可以看到打印出来的Promise状态还是pending,按理说应该是fulfilled的。这是为什么呢?

因为返回的Promise的状态是由它的resolve和reject决定的,而在上面的then方法中,this.PromiseState == "pending"​这一个判断分支里并没有调用返回的Promise的resolve或reject,所以返回的Promise状态是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
72
then(onResolved, onRejected) {
// then函数返回一个Promise
return new Promise((resolve, reject) => {
if (this.PromiseState == "fulfilled") {
// 如果在回调函数中抛出错误,返回的Promise应该是失败状态的,因此要捕获回调函数的异常,把异常传给reject
try {
// 要获取回调函数的返回值
let result = onResolved(this.PromiseResult);
// 如果返回值是一个Promise,这个then返回的Promise状态由这个Promise决定
if (result instanceof Promise) {
result.then(res => {
resolve(res)
}, err => {
reject(err)
})
} else {
resolve(result)
}
} catch (e) {
reject(e)
}
}
if (this.PromiseState == "rejected") {
// 如果在回调函数中抛出错误,返回的Promise应该是失败状态的,因此要捕获回调函数的异常,把异常传给reject
try {
// 要获取回调函数的返回值
let result = onRejected(this.PromiseResult);
// 如果返回值是一个Promise,这个then返回的Promise状态由这个Promise决定
if (result instanceof Promise) {
result.then(res => {
resolve(res);
}, err => {
reject(err);
})
} else {
resolve(result);
}
} catch (e) {
reject(e);
}
}
if (this.PromiseState == "pending") {
var callback = {
onResolved: () => {
try {
let result = onRDesolved(this.PromiseResult);
if (result instanceof Promise) {
result.then(r => { resolve(r) }, v => { reject(v) });
} else {
resolve(result);
}
} catch (e) {
reject(e);
}
},
onRejected: () => {
try {
let result = onRejected(this.PromiseResult);
if (result instanceof Promise) {
result.then(r => { resolve(r) }, v => { reject(v) });
} else {
resolve(result);
}
} catch (e) {
reject(e);
}
}
};
this.callbacks.push(callback);
}
})
}

优化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
then(onResolved, onRejected) {
// then函数返回一个Promise
return new Promise((resolve, reject) => {
var callback = (cb) => {
// 如果在回调函数中抛出错误,返回的Promise应该是失败状态的,因此要捕获回调函数的异常,把异常传给reject
try {
// 要获取回调函数的返回值
let result = cb(this.PromiseResult);
// 如果返回值是一个Promise,这个then返回的Promise状态由这个Promise决定
if (result instanceof Promise) {
result.then(res => {
resolve(res)
}, err => {
reject(err)
})
} else {
resolve(result)
}
} catch (e) {
reject(e)
}
}
if (this.PromiseState == "fulfilled") {
callback(onResolved)
}
if (this.PromiseState == "rejected") {
callback(onRejected)
}
if (this.PromiseState == "pending") {
this.callbacks.push({
onResolved: () => {
callback(onResolved)
},
onRejected: () => {
callback(onRejected)
}
});
}
})
}

实现catch方法

在then方法里,已经很完整的实现了异常的回调方法,因此在catch方法中,只需要调用then方法就好了:

1
2
3
catch (onRejected) {
this.then(undefined, onRejected)
}

实现链式调用

Promise是支持链式调用的,因为then的返回值是一个then,下面用原始的Promise测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
let p = new Promise((resolve, reject) => {
resolve(1);
});
p
.then()
.then()
.then()
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
})

代码的第九行会正常打印1。

但是用我们写的Promise,报错了:​

看看代码的第48行:

这里报的是cb is not a function,这是因为,在前面的then的,我们没有给它传递回调,导致onResolved,onRejected是undefined。因此我们只要给onResolve和onRejected加上一个默认值就好了,这里可以用ES6的默认参数实现:

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
then(onResolved = v => v, onRejected = r => r) {
// then函数返回一个Promise
return new Promise((resolve, reject) => {
var callback = (cb) => {
// 如果在回调函数中抛出错误,返回的Promise应该是失败状态的,因此要捕获回调函数的异常,把异常传给reject
try {
// 要获取回调函数的返回值
let result = cb(this.PromiseResult);
// 如果返回值是一个Promise,这个then返回的Promise状态由这个Promise决定
if (result instanceof Promise) {
result.then(res => {
resolve(res)
}, err => {
reject(err)
})
} else {
resolve(result)
}
} catch (e) {
reject(e)
}
}
if (this.PromiseState == "fulfilled") {
callback(onResolved)
}
if (this.PromiseState == "rejected") {
callback(onRejected)
}
if (this.PromiseState == "pending") {
this.callbacks.push({
onResolved: () => {
callback(onResolved)
},
onRejected: () => {
callback(onRejected)
}
});
}
})
}

现在就可以正常实现链式调用了。

Promise其他静态方法的实现

Promise还有resolve、reject、all、race四个静态方法,下面是它们的实现方式。

Promise.resolve

Promise.resolve返回一个Promise,这个如果函数接收的不是Promise,那么返回的Promise就是一个成功的Promise,它的结果是传入的值;如果接收的是一个Promise,哪么返回的Promise的状态由这个Promise决定。

1
2
3
4
5
6
7
8
9
10
11
12
13
static resolve(value) {
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(v => {
resolve(v);
}, r => {
reject(r);
})
} else {
resolve(value);
}
})
}

Promise.reject

Promise.reject无论接收什么,都返回一个失败的Promise:

1
2
3
4
5
static reject(value) {
return new Promise((undefined, reject) => {
reject(value);
})
}

Promise.all

Promise.all接收一个Promise数组,这个方法返回一个Promise,等到所有的Promise对象都成功了或有任意一个Promise失败,会把所有的Promise结果返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static all(promises) {
return new Promise((resolve, reject) => {
var res = [];
var count = 0;
for (let i = 0; i < promises.length; i++) {
let promise = promises[i];
promise.then(v => {
res[i] = v;
count++;
if (count == promises.length) {
resolve(res);
}
}, r => {
reject(r)
})
}
})
}

在函数中要创建一个数组来保存Promise的结果,并创建一个变量记录当前完成的Promise个数,当完成的Promise个数等于传入的Promise个数时,就把结果数组返回。

Promise.race

接收一个Promise数组,返回一个Promise,返回的Promise的状态由最先改变状态的拿一个Promise决定,它的实现方法和Promise.all差不多:

1
2
3
4
5
6
7
8
9
10
11
12
static race(promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
let promise = promises[i];
promise.then(v => {
resolve(v);
}, r => {
reject(r)
})
}
})
}

实现回调函数的异步执行

在JavaScript的Event Loop中,代码是按照这样的顺序去执行的:同步代码→微任务→DOM渲染→宏任务。

在Promise中,executor是同步代码,then方法中的回调是异步代码,并属于微任务。setTimeout等定时器属于宏任务。因此下面的代码输出结果应该为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
console.log("script start");
new Promise((resolve, reject) => {
console.log("Promise executor");
resolve(1);
}).then(res => {
console.log(res);
})
setTimeout(() => {
console.log("setTimeout");
})
console.log("script end");
// script start
// Promise executor
// script end
// 1
// setTimeout

但是我们用自己写的Promise来测试上面的代码,输出的结果为:

1
2
3
4
5
// script start
// Promise executor
// 1
// script end
// setTimeout

这是因为这里执行回调是同步执行的,我们只需要把执行回调的代码,包裹在定时器里就好了。

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
class Promise {
PromiseState = "pending";
PromiseResult = null;
// 保存回调函数
callbacks = [];
constructor(executor) {
var resolve = (value) => {
// 如果Promise状态被修改过,就不能在修改了
if (this.PromiseState != "pending") {
return;
}
this.PromiseState = "fulfilled";
this.PromiseResult = value;
// 执行回调
setTimeout(() => {
this.callbacks.forEach(item => {
item.onResolved(value);
});
})
}

var reject = (reason) => {
// 如果Promise状态被修改过,就不能在修改了
if (this.PromiseState != "pending") {
return;
}
this.PromiseState = "rejected";
this.PromiseResult = reason;
// 执行回调
setTimeout(() => {
this.callbacks.forEach(item => {
item.onRejected(reason);
});
})
}

// 因为在executor中抛出异常,回调用reject,所以要用try...catch捕捉异常
try {
executor(resolve, reject);
} catch (e) {
reject(e);
}
}

then(onResolved = v => v, onRejected = r => r) {
// then函数返回一个Promise
return new Promise((resolve, reject) => {
var callback = (cb) => {
// 如果在回调函数中抛出错误,返回的Promise应该是失败状态的,因此要捕获回调函数的异常,把异常传给reject
try {
// 要获取回调函数的返回值
let result = cb(this.PromiseResult);
// 如果返回值是一个Promise,这个then返回的Promise状态由这个Promise决定
if (result instanceof Promise) {
result.then(res => {
resolve(res)
}, err => {
reject(err)
})
} else {
resolve(result)
}
} catch (e) {
reject(e)
}
}
if (this.PromiseState == "fulfilled") {
setTimeout(() => {
callback(onResolved)
})
}
if (this.PromiseState == "rejected") {
setTimeout(() => {
callback(onRejected)
})

}
if (this.PromiseState == "pending") {
this.callbacks.push({
onResolved: () => {
callback(onResolved)
},
onRejected: () => {
callback(onRejected)
}
});
}
})
}

catch(onRejected) {
this.then(undefined, onRejected)
}

static resolve(value) {
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(v => {
resolve(v);
}, r => {
reject(r);
})
} else {
resolve(value);
}
})
}

static reject(value) {
return new Promise((undefined, reject) => {
reject(value);
})
}

static all(promises) {
return new Promise((resolve, reject) => {
var res = [];
var count = 0;
for (let i = 0; i < promises.length; i++) {
let promise = promises[i];
promise.then(v => {
res[i] = v;
count++;
if (count == promises.length) {
resolve(res);
}
}, r => {
reject(r)
})
}
})
}

static race(promises) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
let promise = promises[i];
promise.then(v => {
resolve(v);
}, r => {
reject(r)
})
}
})
}
}