前言
当我们需要定义一个在特定情况下才会被触发的函数时,
一个在特定情况下被调用的函数,我们一般称为回调函数.
比如我们需要在100秒后执行一个输出,那么我们可能会这样写1
2
3setTimeout(() => {
console.log('已经过了100ms了');
}, 100);
但我们常常需要在回调函数中,再去定义另一个回调,常常会陷入回调地狱.例如这样1
2
3
4
5
6
7
8
9
10
11setTimeout(() => {
console.log('已经过了100ms了');
setTimeout(() => {
console.log('又过了100ms了');
setTimeout(() => {
console.log('又双叒叕过了100ms了');
// 以此类推
}, 100);
}, 100);
}, 100);
Promise
为了解决回调地狱定义了Promise
当你创建一个Promise仅仅是表示一个承诺,这个承诺将会在未来的某个时候这个承诺将会被兑现,无论是成功(then)\失败(catch)\无论错误失败都进行操作(finally).
MDN上的例子1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20function checkMail() {
return new Promise((resolve, reject) => {
if (Math.random() > 0.5) {
resolve('Mail has arrived');
} else {
reject(new Error('Failed to arrive'));
}
});
}
checkMail()
.then((mail) => {
console.log(mail);
})
.catch((err) => {
console.error(err);
})
.finally(() => {
console.log('Experiment completed');
});
但如果仅仅是这样,其实还没有解决地狱回调的问题
链式调用
Promise还支持另外一种用法,就是在then中的回调返回一个新的promise1
2
3
4
5
6
7
8
9
10
11
12
13checkMail()
.then((mail) => {
readTheMail(mail) // 此处的 readTheMail 函数返回了另外一个 Promise 对象
})
.then((text) => {
console.log(`mail content ${text}`);
})
.catch((err) => {
console.error(err);
})
.finally(() => {
console.log('Experiment completed');
});
组合函数1
有的时候我们需要的结果是需要多个Promise参与的.
需要等待所有完成Promise.all() Promise.allSettled()
all()与allSettled()的区别是,all()在遇到reject时立即结束1
2
3
4
5
6
7
8
9
10const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
// expected output: Array [3, 42, "foo"]1
2
3
4
5
6
7
8
9
10const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];
Promise.allSettled(promises).
then((results) => results.forEach((result) => console.log(result.status)));
// expected output:
// "fulfilled"
// "rejected"
任意一个完成Promise.any() Promise.race()
any()与race()的区别是,any()只返回第一个成功的,race()不论成功失败
捕获未捕获的异常2
1 | window.addEventListener('unhandledrejection', event => ···); |
还是有些不方便的地方
Promise虽然能够减轻回调地狱,但是在以下的情况,还是令人十分难受
- 需要在Promise链中共享变量3
- 使用高阶作用域变量: 贡献的变量过多,导致代码冗余
- 嵌套作用域: 本来Promise就是为了回避回调地狱
- return多个值: 不能将变量传递到.catch()与finally()中;当共享变量过多,或者需要跨过数个.then(),需要return的值会很多
- Promise链需要从Promise开始,普通函数需要借助Bluebird
- Promise 链中返回的错误栈没有给出错误发生位置
- 进行调试时,不便进行断点
使用Async/Await进行简化
Async/Await是基于Promise之上的语法糖.简化了异步操作代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18var sleep = function (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve();
}, time);
})
};
// 定义了一个异步函数
async function start () {
console.log('start');
// 在这里使用起来就像同步代码那样直观
await sleep(3000);
// await会等待当前行的 Promise 执行完毕后,在执行后续的内容,等待的过程中,不会影响此方法体之外语句的执行
console.log('end');
};
start();// 函数的返回值是 Promise 对象
链式调用的转化
当我们使用Promise需要链式调用或者进行变量共享时,Async/Await结构显然更加清晰1
2
3
4
5
6
7
8
9
10
11
12
13async function getUserInfo() {
const api = new Api();
const user = await api.getUser();
const friends = await api.getFriends(user.id);
const photo = await api.getPhoto(user.id);
return { user, friends, photo };
}
function promiseUserInfo() {
getUserInfo().then(({ user, friends, photo }) => {
console.log("promiseUserInfo", { user, friends, photo });
});
}
错误捕获
使用try/catch进行处理就行1
2
3
4
5
6
7
8
9async function getLotsOfUserDataFaster() {
try {
const userPromises = Array(10).fill(getUserInfo());
const users = await Promise.all(userPromises);
console.log("getLotsOfUserDataFaster", users);
} catch (err) {
console.error(err);
}
}
与组合函数一起使用
1 | async function fetchOneUrlTwice() { |
使用时的注意点
1 | async function fun() { |
因为此处forEach中的回调函数相当于创建了一个Promise所以就立即返回了,并不会等待fetch执行完成
如果想要一条一条阻塞执行,看下面的例子1
2
3
4
5
6
7// 转成普通的循环
async function fun() {
for (let i of [1, 2, 3]) {
await fetch(`url/${i}`);
}
}
fun();
如果要使上面的例子转为并发执行的话1
2
3
4
5
6
7
8
9
10
11
12
13
14async function fun() {
const requests = [
fetch(`url/1`),
fetch(`url/2`),
fetch(`url/3`)
];
for await(let resp of requests) {
// ...
}
或者
await Promise.all(requests);
}
fun();
参考链接
- 如何在Promise链中共享变量
- Async/Await是这样简化JavaScript代码的
- 重构:从Promise到Async/Await
- 一个真实的Async/Await示例
- 掌握 Async/Await
1. Promise 中的三兄弟 .all(), .race(), .allSettled() ↩
2. Window: unhandledrejection event ↩
3. 如何在Promise链中共享变量 ↩