Fork me on GitHub

promise理解与实现

区分并发和并行

并发和并行区别是什么?

  • 并发是宏观概念,假设分别有任务A和任务B,在一段时间内通过任务间的切换完成了这两个任务,这种情况可以称为并发。
  • 并行是微观概念,假设cpu存在两个核心,那么就可以同时完成任务A、B,同时完成多个任务的情况就可以称为并行。

普通的回调函数有什么缺点?

致命的弱点,就是容易写出回调地狱(Callback hell),让代码看起来不利于阅读和维护,根本问题:

  1. 嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身
  2. 嵌套函数一多,就很难处理错误
  3. 不能使用 try catch 捕获错误,不能直接 return。

Promise

Promise表示一个异步操作的最终结果,与之进行交互的方式主要是then方法,该方法注册了两个回调函数,用于接收promise的终值或者promise不能执行的原因。

Promise是承诺的意思,这个承诺有三种状态:

  1. 等待态 pending
  2. 完成态 resolved
  3. 拒绝态 rejected

这个承诺一旦从等待状态变成其他状态就不能更改了,例如:

1
2
3
4
5
6
7
8
9
10
11
new Promise((resolve, reject) => {
resolve('success')
// 无效
reject('reject')
})
/*
Promise {<resolved>: "success"}
__proto__: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: "success"
*/

在构造Promise的时候,构造函数内部的代码是立即执行的

1
2
3
4
5
6
7
8
new Promise((resolve, reject) => {
console.log('new Promise')
resolve('success')
})
console.log('finifsh')

// new Promise
// finifsh
  • Promise 实现了链式调用,也就是说每次调用 then 之后返回的都是一个 Promise,并且是一个全新的 Promise,原因也是因为状态不可变。
  • 如果你在 then 中 使用了 return,那么 return 的值会被 Promise.resolve() 包装。
1
2
3
4
5
6
7
8
Promise.resolve(1)
.then(res => {
console.log(res) // => 1
return 2 // 包装成 Promise.resolve(2)
})
.then(res => {
console.log(res) // => 2
})

如何实现一个简易版promise?

  1. 创建三个常量表示promise的状态
  2. 在函数体内部首先保存this,因为代码可能会异步执行,用于获取正确的 this对象
  3. 开始promise的状态应该是pending
  4. value 变量用于保存 resolve 或者 reject 中传入的值
  5. resolvedCallbacks 和 rejectedCallbacks 用于保存 then 中的回调,因为当执行完 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
const PENDING = 'pending' // 等待态
const RESOLVED = 'resolved' // 执行态
const REJECTED = 'rejected' // 拒绝态

function Promise1(fn) {
// 保存this
const that = this;
// 初始化state
that.state = PENDING;
// 保存resolve中传入的值
that.value = null;
// 保存reject中传入的值
that.reason = null;
// then注册的成功回调方法
that.resolvedCallbacks = [];
// then注册的失败回调方法
that.rejectedCallbacks = [];

// 完善 resolve 和 reject 函数,添加在Promise2函数体内部
function resolve(value) {
if (that.state === PENDING) {
that.state = RESOLVED
that.value = value
that.resolvedCallbacks.map(cb => cb(that.value))
}
}

function reject(reason) {
if (that.state === PENDING) {
that.state = REJECTED
that. reason = reason
that.rejectedCallbacks.map(cb => cb(that.value))
}
}

// 执行Promise中传入的函数
try {
fn(resolve, reject)
} catch (e) {
reject(e)
}
}

关于resolve、reject两个函数的说明:

  • 首先两个函数都要判断当前状态是否为等待中,只有等待中才可以改变状态
  • 将当前状态更改为对应状态,并且将传入的值赋给value
  • 遍历回调数组并执行

关于执行Promise中传入的函数的说明:

  • 执行传入的参数并且将之前两个函数当做参数传进去
  • 当执行异步操作时有可能发生异常,需要try/catch捕获到异常,并使promise进入rejected状态

最后是then函数的实现:

  • 熟悉判断两个参数是否为函数类型,因为这两个参数是可选参数
  • 当参数不是函数类型,需要创建一个函数赋值给对应的参数,同时也实现了透传,例如:
1
2
3
// 该代码目前在简单版中会报错
// 只是作为一个透传的例子
Promise.resolve(4).then().then((value) => console.log(value))
  • 然后就是一系列判断状态的逻辑,当状态不是等待态时,就去执行相对应的函数。如果状态是等待态的话,就往回调函数中 push 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Promise1.prototype.then = function(onFulfilled, onRejected) {
const that = this;
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r }

if (that.state === PENDING) {
that.resolvedCallbacks.push(onFulfilled);
that.rejectedCallbacks.push(onRejected);
}

if (that.state === RESOLVED) {
onFulfilled(that.value);
}

if (that.state === REJECTED) {
onRejected(that.reason);
}
}

测试用例1:

1
2
3
4
5
6
7
8
9
let promise = new Promise1((resolve, reject) => {
resolve(1)
});

promise.then((data) => {
console.log(data)
}, (err) => {
console.log(err)
})

测试用例2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let promise = new Promise1(function(resolve,reject){
setTimeout(() => {
resolve(1)
})
})
promise.then(function(data){
console.log('data1',data);
},function(err){
console.log('err',err);
})
promise.then(function(data){
console.log('data2',data);
},function(err){
console.log('err',err);
})
promise.then(function(data){
console.log('data3',data);
},function(err){
console.log('err',err);
})

// data1 1
// data2 1
// data3 1

⚠️ 简易版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
50
51
52
53
54
55
56
57
58
59
60
61
const PENDING = 'pending' // 等待态
const RESOLVED = 'resolved' // 执行态
const REJECTED = 'rejected' // 拒绝态

function Promise1(fn) {
// 保存this
const that = this;
// 初始化state
that.state = PENDING;
// 保存resolve中传入的值
that.value = null;
// 保存reject中传入的值
that.reason = null;
// then注册的成功回调方法
that.resolvedCallbacks = [];
// then注册的失败回调方法
that.rejectedCallbacks = [];

// 完善 resolve 和 reject 函数,添加在Promise2函数体内部
function resolve(value) {
if (that.state === PENDING) {
that.state = RESOLVED
that.value = value
that.resolvedCallbacks.map(cb => cb(that.value))
}
}

function reject(value) {
if (that.state === PENDING) {
that.state = REJECTED
that.value = value
that.rejectedCallbacks.map(cb => cb(that.value))
}
}

// 执行Promise中传入的函数
try {
fn(resolve, reject)
} catch (e) {
reject(e)
}
}

Promise1.prototype.then = function(onFulfilled, onRejected) {
const that = this;
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r }

if (that.state === PENDING) {
that.resolvedCallbacks.push(onFulfilled);
that.rejectedCallbacks.push(onRejected);
}

if (that.state === RESOLVED) {
onFulfilled(that.value);
}

if (that.state === REJECTED) {
onRejected(that.reason);
}
}

Promise A+ 规范

核心的 Promises/A+ 规范不设计如何创建、解决和拒绝 promise,而是专注于提供一个通用的 then 方法。

详细内容参考参考资料:Promise/A+ 规范

根据规范完善Promise

1.链式回调的异常处理

then中无论是执行成功的回调还是失败回调,只要有返回结果,都会走到下一个then(根据不同返回结果进入下一个then的不同回调)

promise是通过then中返回新的promise来实现链式调用的,一个新的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
Promise1.prototype.then = function(onFulfilled, onRejected) {
const that = this;
let promise2; // then返回新的Promise
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r }

if (that.state === PENDING) {
promise2 = new Promise1(function (resolve, reject) {
that.resolvedCallbacks.push(function () {
//捕获异步回调时的异常,如果有进入promise2的失败态
try {
onFulfilled(self.value);
} catch (e) {
reject(e);
}
});
that.rejectedCallbacks.push(function () {
try {
onRejected(self.value);
} catch (e) {
reject(e);
}
});
});
}

if (that.state === RESOLVED) {
promise2 = new Promise1(function (resolve, reject) {
onFulfilled(that.value);
});
}

if (that.state === REJECTED) {
promise2 = new Promise1(function (resolve, reject) {
onRejected(that.reason);
});
}

return promise2;
}

2.链式回调的返回值处理

  1. 如果第一个promise返回一个普通值,会走到下一次then成功的回调
  2. 如果第一个promise返回了一个promise,需要等待返回的promise执行后的结果,再传递到下一次then中,所以用变量x来接收第一个then的返回值
  3. x可能是普通值,也可能是promise,需要实现一个resolvePromise方法统一处理返回值

更新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
Promise.prototype.then = function(onFuilled, onRejected) {
let that = this;
let promise2; // then返回新的Promise
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r }
if (that.state === PENDING) {
promise2 = new Promise1(function (resolve, reject) {
that.resolvedCallbacks.push(function () {
//捕获异步回调时的异常,如果有进入promise2的失败态
try {
let x = onFulfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
that.rejectedCallbacks.push(function () {
try {
let x = onRejected(self.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}

if (that.state === RESOLVED) {
promise2 = new Promise1(function (resolve, reject) {
let x = onFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject);
});
}

if (that.state === REJECTED) {
promise2 = new Promise1(function (resolve, reject) {
let x = onRejected(that.reason);
resolvePromise(promise2, x, resolve, reject);
});
}

return promise2;
}

实现兼容多种Promise的resolvePromise函数

1
2
3
4
5
6
7
8
9
10
11
12
/**
p2 第二次then的promise实例
x 第一次then的返回值
resolve/reject : p2的 resolve/reject
*/

function resolvePromise(p2, x, resolve, reject) {
// 首先规范规定了 x 不能与 promise2 相等,这样会发生循环引用的问题
if (p2 === x) {
return reject(new TypeError('循环引用Error'));
}
}

然后需要判断x的类型

1
2
3
4
5
if (x instanceof Promise1) {
x.then(function(value) {
resolvePromise(promise2, value, resolve, reject);
}, reject);
}

这里的代码是完全按照规范实现的。如果 x 为 Promise 的话,需要判断以下几个情况:

  1. 如果 x 处于等待态,Promise 需保持为等待态直至 x 被执行或拒绝
  2. 如果 x 处于其他状态,则用相同的值处理 Promise

以上这些是规范需要我们判断的情况,实际上不判断状态也是可行的。
接下来继续按照规范来实现剩余的代码

promise的处理方式是,一旦进入成功态,就成功了,不会再调用reject,反之亦然
这里通过在resolvePromise方法中,加入called 标识,表示已经进入一个resolve或reject;若果called为true,直接返回,如果为false,置为true

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
let called = false; // 表示是否调用过成功或者失败
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
if (typeof then === 'function') {
//认为x是一个promise,立刻执行该promise的then方法
//如果x进入成功态,会触发成功回调
then.call(x, function (y) {
if (called) return;
called = true;
//y可能还是一个promise,继续解析,直到返回的是一个普通值
resolvePromise(p2,y,resolve,reject);
}, function (err) {
if (called) return;
called = true;
reject(err);
});
} else {
//如果then不是方法,直接认为返回一个对象,调用p2成功态
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else { // 说明是一个普通值
resolve(x);
}
  • 首先创建一个变量 called 用于判断是否已经调用过函数
  • 然后判断 x 是否为对象或者函数,如果都不是的话,将 x 传入 resolve 中
  • 如果 x 是对象或者函数的话,先把 x.then 赋值给 then,然后判断 then 的类型,如果不是函数类型的话,就将 x 传入 resolve 中
  • 如果 then 是函数类型的话,就将 x 作为函数的作用域 this 调用之,并且传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise,两个回调函数都需要判断是否已经执行过函数,然后进行相应的逻辑
  • 以上代码在执行的过程中如果抛错了,将错误传入 reject 函数中

3.实现catch

捕获错误的方法,catch相当于一个then调用错误方法

1
2
3
Promise1.prototype.catch = function(callback) {
return this.then(null, callback)
}

4.实现Promise.all

Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。

all接收一个成员为promise实例的数组,依次执行,并按顺序返回执行结果
当所有promise都执行成功,就进入成功态,有一个执行失败了,就进入失败态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// promises是一个promise数组
Promise1.all = function (promises) {
return new Promise1(function (resolve, reject) {
// 最终返回值的结果
let arr = [];
// 表示成功了多少次
let count = 0;
function processData (index, y) {
arr[index] = y;
// 所有promise执行成功,进入成功态
if (++count === promises.length) {
resolve(arr);
}
}

for (let i = 0; i < promises.length; i++) {
promises[i].then(function (y) {
processData(i, y);
}, reject); //有一个失败,就调用失败回调
}
});
}

5.实现Promise.race

参数同all,只要有一个promise成功了,就算成功。如果有一个失败了,就失败了,其他promise继续执行

1
2
3
4
5
6
7
Promise1.race = funciton (promises) {
return new Promise1(function (resolve, reject) {
for (let i = 0; i < promises.length; i++) {
promises[i].then(resolve, reject);
}
});
}

6.实现Promise.resolve/Promise.reject

Promise.resolve 可以理解为 生成一个成功的promise

1
2
3
4
5
Promise1.resolve = function (value) {
return new Promise1(function (resolve, reject) {
resolve(value);
});
}

Promise.reject 即生成一个失败的promise

1
2
3
4
5
Promise1.reject = function (value) {
return new Promise1(function (resolve, reject) {
resolve(reason);
});
}

Promises/A+规范明确要求回调需要通过异步方式执行,用以保证一致可靠的执行顺序

⚠️ 符合Promise A+ 规范的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
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
// 定义三个状态
const PENDING = 'pending'; // 等待态
const RESOLVED = 'resolved'; // 完成态
const REJECTED = 'rejected'; //拒绝态

function Promise1 (fn) {
// 获取正确的this
const that = this;
// 初始化状态
that.state = PENDING;
// 保存resolve中传入的值
that.value = null;
// 保存reject中传入的值
that.reson = null;
// 保存完成态的回调函数
that.resolvedCallbacks = [];
// 保存拒绝态的回调函数
that.rejectedCallbacks = [];

function resolve (value) {
// 判断传入值是否为Promise类型
if (value instanceof Promise1) {
return value.then(resolve, reject);
}

// 实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。
setTimeout(() => {
if (that.state === PENDING) {
that.state = RESOLVED;
that.value = value;
that.resolvedCallbacks.forEach(cb => cb(that.value));
}
}, 0);
}

function reject (reason) {
setTimeout(() => {
if (that.state === PENDING) {
that.state = REJECTED;
that.reason = reason;
that.rejectedCallbacks.forEach(cb => cb(that.reason));
}
});
}

try {
fn (resolve, reject);
} catch (e) {
reject(e);
}
}

Promise1.prototype.then = function (onFulfilled, onRejected) {
const that = this;
// 每个then函数都需要返回一个新的Promise对象,promise2用于保存新的返回对象
let promise2;
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : r => {
throw r}

if (that.state === PENDING) {
promise2 = new Promise1(function (resolve, reject) {
that.onFulfilledCallbacks.push((value) => {
try {
let x = onFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});

that.rejectedCallbacks.push((value) => {
try {
let x = onRejected(that.reason);;
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}

if (that.state === RESOLVED) {
promise2 = new Promise1(function (resolve, reject) {
setTimeout(function () {
try {
let x = onFulfilled(that.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}

if (that.state === REJECTED) {
promise2 = new Promise1(function (resolve, reject) {
setTimeout(function () {
try {
let x = onRejected(that.reason);;
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}

return promise2;
}

function resolvePromise(promise2, x, resolve, reject) {
// x可能是别人的promise
if (promise2 === x) {
return reject(new TypeError('Error'));
}
// 表示是否调用过成功或者失败
let called;
// 检查x是不是一个promise,promise应该是一个对象
if (x !== null && typeof x === 'object' || typeof x === 'function') {
// 可能是promise {},看这个对象中是否有then方法,如果有then就认为是promise了
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, function (y) {
if (called) return;
called = true;
// y可能还是一个promise,再去解析直到返回的是一个普通值
resolvePromise(promise2, x, resolve, reject);
}, function (err) {
if (called) return
called = true;
reject(err);
});
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true;
reject(err);
}
} else { // 说明是一个普通值
resolve(x); // 表示成功了
}
}

// 捕获错误的方法,相当于一个then调用错误方法
Promise1.prototype.catch = function (callback) {
return this.then(null, callback);
}

// 解析全部方法,参数promises是一个promise数组
Promise1.all = function (promises) {
return new Promise1(function (resolve, reject) {
// 返回值数组
let arr = [];
// 表示成功的次数
let i = 0;
function processData (index, y) {
arr[index] = y;
// 全部成功,进入成功态
if (++i === promises.length) {
resolve(arr);
}
}

for (let i = 0; i < promises.length; i++) {
promises[i].then(function (y) {
processData(i, y);
}, reject); //有一个失败,就调用失败回调
}
});
}

// 只要有一个promise成功了就算成功。如果第一个失败了就失败了
Promise1.race = function (promises) {
return new Promise1(function (resolve, reject) {
for (let i = 0; i < promises.length; i++) {
promises[i].then(resolve, reject);
}
});
}

// 生成一个成功的promise
Promise1.resolve = function (value) {
return new Promise1(function (resolve, reject) {
resolve(value);
});
}

// 生成一个失败的promise
Promise1.reject = funciton (reason) {
return new Promise1(function (resolve, reject) {
reject(reason);
});
}

Promise1.defer = Promise1.deferred = function () {
let dfd = {};
dfd.promise = new Promise1(funcition (resolve, reject) {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
}

module.exports = Promise1;

参考文章:
https://juejin.im/post/5ab466a35188257b1c7523d2#heading-1

本文标题:promise理解与实现

文章作者:tongtong

发布时间:2019年03月19日 - 19:03

最后更新:2019年04月15日 - 16:04

原始链接:https://ilove-coding.github.io/2019/03/19/promise理解与实现/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

坚持原创技术分享,您的支持将鼓励我继续创作!
-------------本文结束-------------