原文发布时间: 2020-07-29
你能指出这块代码存在的问题么?
async function getPeople() {
const members = await fetch("/members");
const nonMembers = await fetch("/non-members");
return members.concat(nonMembers);
}
花点时间看下。这样的代码可能会存在于很多的JS代码中。即使是认为对它很了解的人也会犯这种错,包括我,正是这个原因促使我写下这篇文章。
给个提示,不使用async/await时代码是这样的:
function getPeople() {
return fetch("/members")
.then(members => fetch("/non-members")
.then(nonMembers => members.concat(nonMembers)))
}
明白了吗,,一般我们不会写出以上代码,因为没有async/await,这种错误是很难犯的。
这时浏览器network面板的请求日志是这样的
我们创建了2个独立的异步任务,并将它们放入一个序列中。getPeople函数执行的时间是它需要的2倍。
正确的代码应该是这样:
async function getPeople() {
const members = fetch("/members");
const nonMembers = fetch("/non-members");
const both = await Promise.all([ members, nonMembers ]);
return both[0].concat(both[1]);
}
不使用aysnc/await:
function getPeople() {
const members = fetch("/members");
const nonMembers = fetch("/non-members");
return Promise.all([ members, nonMembers ])
.then(both => both[0].concat(both[1]));
}
此时浏览器请求日志:
async/await 运行说明
async function foo() {
const result1 = await new Promise((resolve) => setTimeout(() => resolve('1')))
console.log(result1)
const result2 = await new Promise((resolve) => setTimeout(() => resolve('2')))
console.log(result2)
}
foo()
console.log(3)
// 输出顺序: 3 1 2
- foo函数的第一行将会同步执行,await将会等待promise的结束。然后暂停通过foo的进程,并将控制权交还给调用foo的函数。
- 一段时间后,当第一个promise完结的时候,控制权将重新回到foo函数内。示例中将会将1(promise状态为fulfilled)作为结果返回给await表达式的左边即result1。接下来函数会继续进行,到达第二个await区域,此时foo函数的进程将再次被暂停。
- 一段时间后,同样当第二个promise完结的时候,result2将被赋值为2,之后函数将会正常同步执行,将默认返回undefined 。
很容易犯的错误
即使你理解了async/await语法最终转换为的Promise代码,也很容易陷入这个陷阱。如果你还不了解Promise以及async/await到底在做什么,那就更不用说了。
不能说async/await是“不好的”(或者“有害的”)。特别是在没有像promise这样合理的API的语言中,或者在闭包中有其他限制,使promise不那么容易使用的语言中,async/await可以使代码总体上更具可读性。
但它们也有一个相当大的陷阱:它允许我们获取异步的东西,并假装它们是同步的。await 关键字会阻塞其后的代码,直到promise完成,就像执行同步操作一样。它确实可以允许其他任务在此期间继续运行,但await后的代码被阻塞。
这意味着代码可能会因为大量await的promises相继发生而变慢。每个await都会等待前一个完成,而你实际想要的是所有的这些promises同时开始处理(就像我们没有使用async/await时那样)。
一般async/await以一种可能永远不会被注意到的微妙方式出错:它只会影响性能,而不会影响正确性。
缓解问题
-
如果你还不清楚你的代码怎么运行,去看浏览器的请求瀑布流,看看你是否可以提高程序运行速度。
-
你也可以使用这个经验法则:如果一个await函数没有使用另一个await函数调用的结果(或从结果到处东西),你应该使用Promise.all()使它们同步执行。