Promise含义
Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise是一个对象,从它可以获取异步操作的消息。Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。
Promise特点
Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
Promise实现
实现思路:将then方法中的第一个参数 (onResolve) 依次塞进成功队列 resolveList 中,当 resolve(value) 时,遍历 resolveList 中的事件,将 value 传递并更新至最后一个then方法结束;若then方法中有第二个参数 (onReject) 且 rejectCallback 为 null 时,将 onReject 拷贝到 rejectCallback ,这样就实现了 reject(error) 时只会将 error 传递到第一个出现 onReject 参数的then方法里面。
因为我是在node环境写的,所以这里就直接使用es6的class实现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
53class MyPromise {
constructor(executor) {
let self = this;
this.status = 'pending'; // promise状态, 作用:避免执行多次resolve
this.data = null; // 传递的数据
this.resolveList = []; // 成功列表
this.rejectCallback = null; // 失败回调
// 成功回调
function resolve(data) {
if (self.status === 'pending') {
self.status = 'resolved';
self.data = data;
self.resolveList.forEach(callback => {
// 更新回调列表传递的值
let newData = callback(self.data);
self.data = newData;
})
}
}
// 失败回调
function reject(reason) {
if (self.status === 'pending') {
self.status = 'rejected';
self.data = reason;
self.rejectCallback(reason)
}
}
executor(resolve, reject)
}
then(onResolve, onReject) {
this.resolveList.push(onResolve);
// 检查触发then时是否有填写第二个参数, 若有则error将不返回错误到catch方法中,直接返回到第一个出现reject回调的then方法中
if (onReject && !this.rejectCallback) {
this.rejectCallback = onReject
}
// 返回实例使它可以继续使用then方法
return this
}
catch(callback) {
// 判断前面的then调用时是否填写了第二个参数
if (!this.rejectCallback) {
this.rejectCallback = callback;
}
}
}
这样就简单的实现了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
27let test = new MyPromise((resolve, reject) => {
console.log('三秒后出结果...')
let num = Math.random();
setTimeout(() => {
num > 0.5 ? resolve(num) : reject(num)
}, 3000)
}).then(value => {
console.log('then1:', value)
return value + 1
}).then(value => {
console.log('then2:', value)
}).catch(err => {
console.log('出错了:', err)
})
node运行后可能得到的结果是:
三秒后出结果...
then1: 0.7013671932574768 // 这个值是随机数
then2: 1.7013671932574768
也可能是:
三秒后出结果...
出错了: 0.11785923049044666
这时候我们去掉setTimeout看看结果是什么:1
三秒后出现结果...
这时候我们就会看到没有出现结果,这是因为不使用定时器直接执行会导致 resolve() 在执行的时候 then 方法还没有执行,也就是 resolveList 长度为0,这是由于Promise回调函数延迟绑定导致的,Promise使用了微任务 queneMicrotask 解决这个问题,这里我们就用宏任务的定时器模拟微任务,修改了 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
27function resolve(data) {
// 用宏任务代替queueMicrotask模拟promise微任务
setTimeout(() => {
if (self.status === 'pending') {
self.status = 'resolved';
self.data = data;
self.resolveList.forEach(callback => {
// 更新回调列表传递的值
let newData = callback(self.data);
self.data = newData;
})
}
})
}
// 失败回调
function reject(reason) {
// 用宏任务代替queueMicrotask模拟promise微任务
setTimeout(() => {
if (self.status === 'pending') {
self.status = 'rejected';
self.data = reason;
self.rejectCallback(reason)
}
})
}
这样就能不使用定时器也能看到结果了。
接着我们来实现 Promise.all: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
31static all(promises) {
return new MyPromise((resolve, reject) => {
let result = [];
promises.forEach(promise => {
promise.then(value => {
result.push(value)
if (result.length === promises.length) {
resolve(result)
}
})
})
})
}
/** Promise.all 测试 */
let p1 = new MyPromise(resolve => {
setTimeout(() => {
resolve(1)
}, 2000)
})
let p2 = new MyPromise(resolve => {
setTimeout(() => {
resolve(2)
}, 3000)
})
MyPromise.all([p1, p2]).then(result => console.log('result:', result))
输出结果:
result: [1, 2]
这里只实现了all,其他的像race, resolve, reject实现原理都差不多,这里就不一一实现了,完整代码在github上。