Promise简述

Promise是ES6中新增的一个类:Promise 承诺/约定模式,基于这种模式可以处理异步编程

需求场景:

首先从服务器基于’/api/1’接口获取数据,成功后基于’/api/2’获取数据,得到返回结果后,再基于’/api/3’获取数据。这种处理方式被称为Ajax串行。

异步的Ajax请求(不管成功或者失败,都继续干其他事)

1
2
3
4
5
6
7
8
9
10
$.ajax({
url: 'http://127.0.0.1:5500/server/api/1',
method: 'get',
success: (res) => {
data = res;
console.log(res) // => 1
}
})

console.log(data) // => null

如果想要同步Ajax请求,可以用回调函数嵌套回调函数:(第一个请求成功才能发第二个)[回调地狱]

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
$.ajax({
url: 'http://127.0.0.1:5500/server/api/2',
method: 'get',
success: (res) => {
console.log(res)
$.ajax({
url: 'http://127.0.0.1:5500/server/api/1',
method: 'get',
success: (res) => {
console.log(res)
$.ajax({
url: 'http://127.0.0.1:5500/server/api/3',
method: 'get',
success: (res) => {
console.log(res)
}
})
}
})
}
})
-------
2
1
3

此时我们需要一种优秀的代码管理模式,能够有效的管理异步编程中的代码,通过这种代码管理思想,让代码开发起来更便捷,维护起来也更方便、可读性也更强 。 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
const api1 = function () {
return new Promise(resolve => {
$.ajax({
url: 'http://127.0.0.1:5500/server/api/1',
method: 'get',
success(result){
resolve(result)
}
})
})
}
const api2 = ...
const api3 = ...

api1().then(res=>{
console.log(res)
return api2()
})
.then(res=>{
console.log(res)
return api3()
})
.then(res=>{
console.log(res)
})
---------
2
1
3

基于Promise管理,使用Async和Await表现为同步的效果

1
2
3
4
5
6
7
8
9
10
(async function a() {
let result = await api1();
console.log(result);

result = await api2();
console.log(result);

result = await api3();
console.log(result);
}())

在JS中,除了Ajax请求是异步编程,还有一些操作也是异步编程:

  1. 事件绑定
  2. 定时器
  3. Promise/async/await
  4. requestAnimationFrame

new Promise()必须要传一个executor函数,不然会报错,executor函数会立刻被执行(不是异步)

1
2
3
4
5
6
new Promise(); // Uncaught TypeError: Promise resolver undefined is not a function

function executor(resolve,reject){
/....../
}
new Promise(executor);

在executor函数中一般用来管控异步的操作(不写异步的也可以)

而且传递给executor函数两个参数:resolve、reject,并且这两个参数都是函数

创建一个Promise类的实例p1

每个实例都有PromiseState和PromiseResult两个属性

[[PromiseState]]是Promise的状态,有pending准备状态、fulfilled/resolved成功(已兑现) 、rejected失败(已拒绝)

[[PromiseResult]]是Promise的值,默认时Undefined,一般存储成功的结果或者失败的原因

Promise.prototype 上有then/catch/finally方法

1
2
3
4
5
6
7
8
let p1 = new Promise((resolve, reject) => {
// 执行resolve,将实例状态变为成功,结果是100
resolve(100);
// 执行rejected,将实例的状态变为失败,结果是-1
reject(-1)
// 一旦状态从初始状态pending发生改变,都无法再次改变其状态
// 如果executor函数代码执行报错,则实例状态也会变为失败,并且PromiseResult是失败原因
});

.then控制状态成功之后做什么、失败之后做什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let p1 = new Promise((resolve, reject) => {
// new Promsie的时候立刻执行executor函数,在executor函数中管理了一个异步变成代码[此时状态是pending];当异步操作到达指定时间开始执行(理解为异步操作成功),此时我们通过执行resolve,把promise的状态修改为fulfilled
// 一旦状态修改为fulfilled/rejected,就可以通知.then执行后续操作
setTimeout(() => {
resolve(100)
}, 1000)
});
p1.then(result => {
// 当p1的实例状态修改为fulfilled的时候,通知传递的第一个函数执行
// result -> [[PromiseResult]]
console.log('成功', result)
}, reason => {
//当p1实例的状态修改为rejected的时候,通知第二个参数执行,reason -> [[PromiseResult]]
console.log("失败", reason)
})

Promise如何管控异步编程的?

  1. new Promise的时候创建一个Promise实例,此时在executor函数中管理了一套异步的代码
  2. 后期等异步操作成功或者失败的时候,执行resolve/reject,以此来控制Promise实例的状态和结果
  3. 根据状态和结果,就可以控制基于.then注入的两个方法中的一个去执行了

异步代码执行顺序(代码如上)?

  1. new Promise
  2. 执行executor:设置一个异步定时器
  3. 执行p1.then注入两个方法,注入的方法会保存起来
  4. 等待1000ms
  5. 执行定时器回调函数:执行resolve改变promise状态和值
  6. 通知之前基于.then注入的两个方法中的一个执行

同步代码执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Promsie中是同步的,但是resolve/reject修改Promise实例状态和值后,需要通知.then方法执行,通知的过程是异步的
let p1 = new Promise((resolve, reject) => {
console.log(1);
// 修改状态和值,并且通知基于then注入的方法执行[问题是.then还没有执行,方法还没被注入,不知道该通知谁来执行,所以此时需要把"通知方法执行操作保存起来,放入到等待任务队列中" -> 这个操作本身是异步的]
resolve(2)
console.log(3);
})
p1.then(result => {
console.log("OK", result);
}, reason => {
console.log("Err", reason);
})
console.log(4);
-------
1
3
4
OK 2

创建一个状态为成功的Promise实例:

1
let p1 = Promise.resolve("OK");

Promise实例状态和值的分析

  1. new Promise出来的Promise实例,由resolve/reject的执行来控制其状态和值;或者executor函数执行失败也可以控制其状态和值 (PromiseStatus / PromiseResult)
  2. 通过执行.then返回的新实例,then注入的方法不论哪个方法执行,只要执行不报错,新实例的状态就是fulfilled,只要执行报错,新实例的状态就是rejected;并且新实例的PromiseResult是方法的返回值[1] ;但是如果方法执行,返回的是一个新的Promise实例,则此实例最后的成功或失败直接决定.then返回实例的成功和失败[2]

[1] 无论p1实例的状态是reject/resolve,都不会影响p2的状态

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
let p1 = Promise.resolve(1)
let p2 = p1.then(result => {
console.log("成功->", result)
return 2;
}, reason => {
console.log("失败->", reason)
});
p2.then(result => {
console.log("成功->", result) // p2实例的promiseResult是p1的返回值
}, reason => {
console.log("失败->", reason)
})
------------
成功-> 1
成功-> 2


let p1 = Promise.reject(1)
let p2 = p1.then(result => {
console.log("成功->", result)
return 2;
}, reason => {
console.log("失败->", reason)
return 2;
});
p2.then(result => {
console.log("成功->", result)
}, reason => {
console.log("失败->", reason)
})
------------
失败-> 1
成功-> 2

[2] 返回的Promise状态决定了新实例的状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let p1 = Promise.resolve(1)
let p2 = p1.then(result => {
console.log("成功->", result)
return Promise.reject(2) // 返回的Promise状态决定了新实例的状态
}, reason => {
console.log("失败->", reason)
});
p2.then(result => {
console.log("成功->", result)
}, reason => {
console.log("失败->", reason)
})
---------
成功-> 1
失败-> 2

对于失败的Promise实例,如果没有编写方法处理其结果,则会在控制台抛出异常[但是不阻碍其余代码执行],下面介绍几种处理Promise实例失败的代码写法:

[1] Promise顺延机制,如果当前.then没有写resolve或者reject方法,但是Promise实例触发了该方法,则会顺延到下面的.then,直到最后,如果都没写,则报错:Uncaught (in promise) NO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Promise.reject("NO")
.then(result => {
console.log("成功->", result);
} /*,reason => {return Promise.reject(reason)}*/ )
.then(null, reason => {
console.log("失败->", reason);
})

---------
失败-> NO


Promise.resolve("YES")
.then(null, reason => {
console.log("失败->", reason);
})
.then(result => {
console.log("成功->", result);
})
----------
成功-> YES

[2] 用catch捕获失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Promise.reject("NO")
.then(result => {
console.log("成功->", result);
})
.then(result => {
console.log("成功->", result);
})
.then(result => {
console.log("成功->", result);
})
.catch(reason => {
console.log("失败->", reason);
})
/*
真实项目中,在多个then链下,其余then方法基本都存放在成功处理的事情,最后一个then存放失败的,这样不论是第一次或者其中某一次导致Promise实例状态失败,都会顺延到最后一个失败的处理函数上进行处理
.then(null,reaso=>{
console.log("失败->", reason);
})
*/

Promise All

都成功,Promise就是成功的,results是按照之前设定的顺序依次存储每个Promise的结果

只要有一个过程失败,立即结束处理,Promise实例也是失败的,PromiseResult记录谁失败和失败的原因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const api1() = ()=>{ /* .... */};
const api1() = ()=>{ /* .... */};
const api1() = ()=>{ /* .... */};
const fn = ()=>{ setTimeout(()=>{resolve(100)},1000)};
const A = Promsie.resolve("AAA");


let p = Promise.all([api1(), api2(), api3(), fn(), A]);
p.then(result => {
console.log("成功->", result);
})
.catch(reason=>{
console.log("失败->",reason)
})
1
2
3
4
5
6
7
8
成功-> (5) ["2↵", "1↵", "3↵", 100, "AAA"]
0: "1↵"
1: "2↵"
2: "3↵"
3: 100
4: "AAA"
length: 5
__proto__: Array(0)

PromiseA+

  1. new Promise的时候创建一个Promise实例,此时在executor函数中管理了一套异步的代码
  2. 后期等异步操作成功或者失败的时候,执行resolve/reject,以此来控制Promise实例的状态和结果
  3. 根据状态和结果,就可以控制基于.then注入的两个方法中的一个去执行了

一个基础的Promise实例:

  • 同步executor函数 / 异步executor函数
  • .then链式调用和穿透功能
  • Promise.all/race方法
  • Promise.resolve/reject/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
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
/*
整体代码架构:
function Promise(){} // Promise构造函数,执行executor方法,定义Promise的属性
function solve_new_promsie_status(){} // 处理then的链式调用,将上一个then的返回值作为下一个then的参数

Promise.resolve = function(){} // 支持Promise.resolve方法创建一个status:fulfilled的Promise实例
Promise.reject = function(){} // 支持Promise.reject方法创建一个status:rejected的Promise实例
Promise.prototype.catch = function(){} // 支持Promise.catch对错误结果进行捕获
Promise.all = function(){} // 支持Promise.all Api,当所有Promise函数运行结束返回结果
Promise.race = function(){} // 支持Promise.race Api 将最快完成的Promise函数结果返回

// 实例化Promise并进行链式调用
let p = new Promise(function executor(resolve,reject){resolve("OK")}).then(function successTodo(res){},function failTodo(err){})

Promise执行步骤:
----------------------------------------------------------------------------------------
1. new Promise(executor),传入executor函数并立刻执行
2. executor有两个参数,分别为resolve和reject,resolve/reject也是两个函数
2.1 执行resolve,会修改Promise实例的状态为fulfilled;
2.2 执行reject会修改Promise实例状态为rejected;
2.3 不论执行哪个,PromiseResult都会变为resolve/reject执行传入的值,如上所示,PromiseResult=OK
3. 当Promise实例的状态和结果修改时,通知.then注入的方法执行
4. 执行.then方法
4.1 如果executor中是同步任务,则根据Promise实例状态执行.then注入的函数
4.2 如果executor中是异步任务,则将then方法push到事件池,等待Promise实例状态被修改,再统一执行
5. then方法执行返回一个新的Promise实例,并且新实例的状态取决于这个then执行的结果
5.1 如果then执行报错,则下一个Promise实例的状态为reject,PromiseResult是报错原因
5.2 如果then正常执行,下一个Promsie实例状态为fulfilled,PromiseResult是then的返回值
5. 链式调用新的实例可以继续基于then方法注入成功后做什么事,失败后做什么事。


ps: [*] 标识ignore,这里不写;只写主题逻辑代码
*/


function Promise(executor) {
// 判断executor是否为函数,如果不是函数,则抛出异常[*]
this.PromiseStatus = 'pending';
this.PromiseResult = undefined;
this.onResolveCallbacks = [];
this.onRejectCallbacks = [];

let resolve = value => {
if (this.PromiseStatus === 'pending') {
this.PromiseStatus = 'fulfilled';
this.PromiseResult = value
this.onResolveCallbacks.forEach(item => item())
}
}
let reject = err => {
if (this.PromiseStatus === 'pending') {
this.PromiseStatus = 'rejected';
this.PromiseResult = err
this.onRejectCallbacks.forEach(item => item())
}
}
try {
executor(resolve, reject);
} catch (err) {
reject(err)
}
}
function modifyPromiseByExcResult(bornPromise, x, resolve, reject) {
// change bornPromise status base on x(previous promise return value)
if (bornPromise === x) return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
if ((typeof x === "object" && x != null) || typeof x === 'function') {
let then = x.then;
if (typeof then === 'function') {
then.call(x, function (y) {
resolve(y);
}, function (r) {
reject(r);
})
} else {
resolve(x)
}

} else {
resolve(x)
}
}

Promise.resolve = function resolve(value) {
return new Promise(function (resolve, reject) {
resolve(value);
})
}

Promise.reject = function reject(error) {
return new Promise(function (resolve, reject) {
reject(error);
})
}

Promise.prototype.catch = function (errorTodo) {
return this.then(null, errorTodo)
}

Promise.all = function all(arr) {
return new Promise((resolve, reject) => {
let index = 0,
results = [];
arr.forEach((item, i) => {
item.then(res => {
index++;
results[i] = res; // 将每一个instance执行结果 按照执行次序 保存下来,等所有instance执行完,再打印结果列表
if (index === arr.length) {
resolve(results)
}
}, err => {
reject(err);
})
})
})
}


Promise.race = function race(arr) {
return new Promise((resolve, reject) => {
arr.forEach(item => {
// 遍历,谁先结束就立刻返回
item.then(res => {
resolve(res)
}, err => {
reject(err)
})
})
})
}

Promise.prototype.then = function (successTodo, errorTodo) {
// 传入成功/失败 执行的方法
// 根据executor执行状态,选择执行then的哪个方法
// 返回一个promise实例

// 穿透
successTodo = typeof successTodo === 'function' ? successTodo : res => res;
errorTodo = typeof errorTodo === 'function' ? errorTodo : rea => { throw rea };

let thenPromsie = new _promise((resolve, reject) => { // A
// 这里的resolve和reject管控then执行的结果
switch (this.promisestatus) {
case 'fulfilled':
setTimeout(() => { // 这里需要加上setTimeout是因为:thenPromise需要A执行完才可以完成实例化,加上微任务可以确保只有同步任务执行完,微任务才会被执行,这样可以保证modifyPromiseByExcResult函数执行时,thenPromise是存在的
try {
let x = successTodo(this.promiseresult);
// 根据函数执行结果,改变then实例状态
modifyPromiseByExcResult(thenPromsie, x, resolve, reject);
} catch (e) {
reject(e)
}
});
case 'rejected':
setTimeout(() => {
try {
let x = errorTodo(this.promiseresult);
// 根据函数执行结果,改变then实例状态
modifyPromiseByExcResult(thenPromsie, x, resolve, reject);
} catch (e) {
reject(e)
}
});
case 'pending':
this.resolveCallbacks.push(() => {
try {
let x = successTodo(this.promiseresult);
// 根据函数执行结果,改变then实例状态
modifyPromiseByExcResult(thenPromsie, x, resolve, reject);
} catch (e) {
reject(e)
}
})
this.rejectCallbacks.push(() => {
try {
let x = errorTodo(this.promiseresult);
// 根据函数执行结果,改变then实例状态
modifyPromiseByExcResult(thenPromsie, x, resolve, reject);
} catch (e) {
reject(e)
}
})

}
})
return thenPromsie;
}

Promise.prototype.CUSTOM = true;

/* =======================================================代码测试部分======================================================= */

let p1 = new Promise(function executor(resolve, reject) {
resolve("OK")
})
.then(res => {
console.log("1->success->", res)
return 100;
}, err => {
console.log("1->error->", err)
return 100
})
.then(res => {
console.log("2->success->", res)
return 200;
})
.then(res => {
console.log("3->success->", res)
return Promise.reject("NO");
}, err => {
console.log("3->error->", err)
return Promise.reject("NO");
})
.catch(err => {
console.log("x->catch->err->", err)
})



const st1 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('st1: 200');
}, 200);
})
}

const st2 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('st2: 100');
}, 100);
})
}

const st3 = function () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('st3: 300');
}, 300);
})
}

Promise.all([st1(), st2(), st3()]).then(res => { console.log(res) }, err => { console.log(err) })

Promise.race([st1(), st2(), st3()]).then(res => { console.log(res) }, err => { console.log(err) })

console.log('END')

代码测试截图(Chromium 89.0.4328.0):

PromiseAplus