异步的 bug
当你的程序同步运行时,除了那些程序本身所做的外,没有发生任何状态变化。 对于异步程序,这是不同的 - 它们在执行期间可能会有空白,这个时候其他代码可以运行。
我们来看一个例子。 我们乌鸦的爱好之一是计算整个村庄每年孵化的雏鸡数量。 鸟巢将这一数量存储在他们的存储器中。 下面的代码尝试枚举给定年份的所有鸟巢的计数。
function anyStorage(nest, source, name) {
if (source == nest.name) return storage(nest, name);
else return routeRequest(nest, source, "storage", name);
}
async function chicks(nest, year) {
let list = "";
await Promise.all(network(nest).map(async name => {
list += `${name}: ${
await anyStorage(nest, name, `chicks in ${year}`)
}\n`;
}));
return list;
}
async name =>
部分展示了,通过将单词async
放在它们前面,也可以使箭头函数变成异步的。
代码不会立即看上去有问题……它将异步箭头函数映射到鸟巢集合上,创建一组Promise
,然后使用Promise.all
,在返回它们构建的列表之前等待所有Promise
。
但它有严重问题。 它总是只返回一行输出,列出响应最慢的鸟巢。
chicks(bigOak, 2017).then(console.log);
你能解释为什么吗?
问题在于+=
操作符,它在语句开始执行时接受list
的当前值,然后当await
结束时,将list
绑定设为该值加上新增的字符串。
但是在语句开始执行的时间和它完成的时间之间存在一个异步间隔。 map
表达式在任何内容添加到列表之前运行,因此每个+ =
操作符都以一个空字符串开始,并在存储检索完成时结束,将list
设置为单行列表 - 向空字符串添加那行的结果。
通过从映射的Promise
中返回行,并对Promise.all
的结果调用join
,可以轻松避免这种情况,而不是通过更改绑定来构建列表。 像往常一样,计算新值比改变现有值的错误更少。
async function chicks(nest, year) {
let lines = network(nest).map(async name => {
return name + ": " +
await anyStorage(nest, name, `chicks in ${year}`);
});
return (await Promise.all(lines)).join("\n");
}
像这样的错误很容易做出来,特别是在使用await
时,你应该知道代码中的间隔在哪里出现。 JavaScript 的显式异步性(无论是通过回调,Promise
还是await
)的一个优点是,发现这些间隔相对容易。