前提
我们习惯于使用传统的回调或事件处理来解决异步问题,但是随着前端工程越来越复杂,该模式面临以下两个问题:
回调地狱:某个异步操作需要等待之前的异步操作完成,无论用回调还是事件,都会陷入不断的嵌套
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//小A心中有三个女神
//有一天,小A决定向第一个女神表白,如果女神拒绝,则向第二个女神表白,
//直到所有的女神都拒绝,或有一个女神同意为止
function biaobai(god, callback) {
console.log(`小A向女神【${god}】发出了表白短信`);
setTimeout(() => {
if (Math.random() < 0.1) {
callback(true);
} else {
callback(false);
}
}, 1000);
}
biaobai("女神1", function(result) {
if (result) {
console.log("女神1答应了,小A很开心!")
} else {
console.log("女神1拒绝了,小A表示无压力,然后向女神2表白");
biaobai("女神2", function(result) {
if (result) {
console.log("女神2答应了,小A很开心!")
} else {
console.log("女神2十分感动,然后拒绝了小A,小A向女神3表白");
biaobai("女神3", function(result) {
if (result) {
console.log("女神3答应了,小A很开心!")
} else {
console.log("小A表示生无可恋!!");
}
})
}
})
}
})异步之间的联系:某个异步操作要等待多个异步操作的结果,对这种联系的处理,会让代码的复杂度剧增
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//当所有的女神回复完成后,他要把所有的回复都记录到日志进行分析
//因为回复是异步的,所以不能放到函数末尾执行。
function biaobai(god, callback) {
console.log(`小A向女神【${god}】发出了表白短信`);
setTimeout(() => {
if (Math.random() < 0.05) {
callback(true);
} else {
callback(false);
}
}, Math.floor(Math.random() * (3000 - 1000) + 1000)); //模拟异步,每个女神回复的时间不同
}
let agreeGod = null; //同意小A的第一个女神 (只能建立一个全局变量)
const results = []; //用于记录回复结果的数组
for (let i = 1; i <= 20; i++) {
biaobai(`女神${i}`, result => {
results.push(result); //每次向数组添加一个女神
if (result) {
console.log(`女神${i}同意了`)
if (agreeGod) {
console.log(`小A回复女神${i}: 不好意思,刚才朋友用我手机,乱发的`)
} else {
agreeGod = `女神${i}`;
console.log(`小A终于找到了真爱`);
}
} else {
console.log(`女神${i}拒绝了`)
}
if (results.length === 20) {
console.log("日志记录", results) //终于得到所有的日志
}
})
}
//记录日志是一个单独的功能,这样穿插到代码中,会使代码易读性变差,并且日后添加其他模块的时候不好扩展或维护ES官方参考了大量的异步场景,总结出了一套异步的通用模型,该模型可以覆盖几乎所有的异步场景,甚至是同步场景,也就是Promise。
通用模型
Promise的基本使用
1 | const pro = new Promise((resolve, reject)=>{ |
使用promise后:
1 | function biaobai(god) { |
细节
未决阶段的处理函数是同步的,会立即执行
1
2
3
4const pro = new Promise((res, rej) => {
console.log('未决阶段') //立即执行
res('data')
})thenable和catchable函数是异步的,就算是立即执行,也会加入到事件队列中等待执行,并且,加入的队列是微队列
1
2
3
4
5
6
7const pro = new Promise((res, rej) => {
res(1)
})
pro.then(res => {
console.log(res) //异步,在微队列里
})
console.log(2) //输出 2 1pro.then可以只添加thenable函数,pro.catch可以单独添加catchable函数
在未决阶段的处理函数中,如果发生未捕获的错误,会将状态推向rejected,并会被catchable捕获
1
2
3
4
5
6
7
8const pro = new Promise((res, rej) => {
throw new Error('ERROR') //可以把状态推向rejected
res('data') //不会执行
})
pro.catch(err => {
console.log(err) //可以捕获到错误信息
})一旦状态推向了已决阶段,无法再对状态做任何更改
1
2
3
4
5const pro = new Promise((res, rej) => {
res(1) //已经推向已决阶段
res(2)//不会执行
rej(3)//不会执行
})
- Promise并没有消除回调,只是让回调变得可控
如何处理回调地狱请看下一篇 ES6 之 Promise(二)