异步的 bug

当你的程序同步运行时,除了那些程序本身所做的外,没有发生任何状态变化。 对于异步程序,这是不同的 - 它们在执行期间可能会有空白,这个时候其他代码可以运行。

我们来看一个例子。 我们乌鸦的爱好之一是计算整个村庄每年孵化的雏鸡数量。 鸟巢将这一数量存储在他们的存储器中。 下面的代码尝试枚举给定年份的所有鸟巢的计数。

  1. function anyStorage(nest, source, name) {
  2. if (source == nest.name) return storage(nest, name);
  3. else return routeRequest(nest, source, "storage", name);
  4. }
  5. async function chicks(nest, year) {
  6. let list = "";
  7. await Promise.all(network(nest).map(async name => {
  8. list += `${name}: ${
  9. await anyStorage(nest, name, `chicks in ${year}`)
  10. }\n`;
  11. }));
  12. return list;
  13. }

async name =>部分展示了,通过将单词async放在它们前面,也可以使箭头函数变成异步的。

代码不会立即看上去有问题……它将异步箭头函数映射到鸟巢集合上,创建一组Promise,然后使用Promise.all,在返回它们构建的列表之前等待所有Promise

但它有严重问题。 它总是只返回一行输出,列出响应最慢的鸟巢。

  1. chicks(bigOak, 2017).then(console.log);

你能解释为什么吗?

问题在于+=操作符,它在语句开始执行时接受list的当前值,然后当await结束时,将list绑定设为该值加上新增的字符串。

但是在语句开始执行的时间和它完成的时间之间存在一个异步间隔。 map表达式在任何内容添加到列表之前运行,因此每个+ =操作符都以一个空字符串开始,并在存储检索完成时结束,将list设置为单行列表 - 向空字符串添加那行的结果。

通过从映射的Promise中返回行,并对Promise.all的结果调用join,可以轻松避免这种情况,而不是通过更改绑定来构建列表。 像往常一样,计算新值比改变现有值的错误更少。

  1. async function chicks(nest, year) {
  2. let lines = network(nest).map(async name => {
  3. return name + ": " +
  4. await anyStorage(nest, name, `chicks in ${year}`);
  5. });
  6. return (await Promise.all(lines)).join("\n");
  7. }

像这样的错误很容易做出来,特别是在使用await时,你应该知道代码中的间隔在哪里出现。 JavaScript 的显式异步性(无论是通过回调,Promise还是await)的一个优点是,发现这些间隔相对容易。