异步调用栈执行间隙

Asynchronous call stack execution gap

我正在阅读“Eloquent JavaScript”,在异步章节的末尾有一个关于异步操作执行间隙的部分,它展示了一个例子。本书下面的示例代码无法运行,因为它使用了一些虚构的场景。

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;
 }

它说这段代码的问题,据我所知,是每个异步调用,例如anyStorage 实际上会连接一个空的 list 并且看不到来自其他异步调用的突变。

我尝试如下重现这个问题:

function delay(n) {
    return Promise.resolve(setTimeout(()=>{}, n*1000));
}

async function asyncSum(a) {
    let sum = 0;
    await Promise.all(a.map(async i => sum += await delay(i)));
    return sum;
}

asyncSum([1,2,3,4]).then(value => console.log(value));

但这按预期工作。它正确打印总和。 那么是我误解了这本书还是我的例子有问题?

原代码中的问题在于:

list += `${name}: ${ await ...

list += 就像做 list = list + - 但第二个列表:

list = list +
       ^^^^

指的是 list 在那一刻 - 在任何异步调用完成之前。最终其中一个将完成并分配给 list。但是一旦下一个解析出来,因为它的list指的是原来的,不是新更新的,之前的结果就会丢失

这是一个演示:

const getMultipliedNum = num => Promise.resolve(num * 3);

(async() => {
  let list = "";
  await Promise.all([1, 2, 3].map(async num => {
    list += `${num}: ${ await getMultipliedNum(num)}\n`;
  }));
  console.log(list);
})();

你的例子有同样的问题,尽管 delay 函数被破坏了——结果是 4,这只是最终 setTimeout 调用的结果(因为 setTimeout returns超时ID)。修复它以演示问题:

function delay(n) {
    return Promise.resolve(n);
}

async function asyncSum(a) {
    let sum = 0;
    await Promise.all(a.map(async i => sum += await delay(i)));
    return sum;
}

asyncSum([1,2,3,4]).then(value => console.log(value));

结果是 4,而不是 10 - 这表明并行循环中的 sum += await somethingElse 不起作用。

另一种看待它的方式:

sum += await somethingElse

就像在做

const currentValueOfSum = sum;
sum = currentValueOfSum + await somethingElse;