异步的 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)的一个优点是,发现这些间隔相对容易。
