为什么在外部作用域中的变量内联等待与简单的等待赋值不同?

ikh*_*vjs 4 javascript promise async-await

在我问这个问题之前,我确实研究了这个问题

我不明白为什么在下面的示例中,输出在run1和 中不同run2

"use strict";

function sleep(ms) {
  return new Promise(resolve =>
    setTimeout(() => {
      resolve(ms);
    }, ms)
  );
}

const seconds = [1000, 3000, 2000];

let output1 = 0;
let output2 = 0;

console.log("start");

(async function run1() {
  await Promise.all(
    seconds.map(async sec => {
      output1 = output1 + (await sleep(sec));
    })
  );
  console.log({ output1 });
})();

(async function run2() {
  await Promise.all(
    seconds.map(async sec => {
      const res = await sleep(sec);
      output2 = output2 + res;
    })
  );
  console.log({ output2 });
})();

console.log("fin");
Run Code Online (Sandbox Code Playgroud)

ef-*_*ank 5

对于第一种情况(输出 1),output1在调用和评估异步函数并等待它之前,“缓存”的当前值。

它或多或少等同于以下内容(执行顺序遵循每个异步函数的睡眠时间):

let old = output1; // 0
output1 = old + 1000;
output1 = old + 2000;
output1 = old + 3000; // 0 + 3000 is the final result
Run Code Online (Sandbox Code Playgroud)

对于第二种情况(输出 2),在缓存当前值之前评估异步函数output2。您最终会得到所有值的总和。

它或多或少相当于:

let old = output2; // 0
output2 = old + 1000;
old = output2; // 1000
output2 = old + 2000;
old = output2; // 3000
output2 = old + 3000; // 3000 + 3000 is the final result
Run Code Online (Sandbox Code Playgroud)

注意:如果你使用值 [1000, 4000, 2000],你会看到结果是 4000,所以只有中间值被添加到输出中(不是 1000+2000,它也将是 3000 与问题的值)

老实说,我原以为第一种情况的结果是 2000(最后一个值)或不确定的(竞争条件)。显然,JavaScript 如何调度每个异步任务有一些细节。

感谢A_A回答了为什么最终结果是 3000(或 4000,带有更新的值):这是最长的延迟,它将最后执行/完成。


附加说明:我对“缓存”的错误选择似乎引起了混乱。让我试着解释一下:

JavaScript 程序通常从上到下、从左到右进行评估(暂时忽略异步调用)。

let y = 6;
let x = 7;
y = y * x;
Run Code Online (Sandbox Code Playgroud)

JavaScript 运行时将“看到”为:

  1. 在 y 中存储 6
  2. 在 x 中存储 7
  3. 记住当前的 y 值 (6)
  4. 记住当前的 x 值 (7)
  5. 6 和 7 相乘 (= 42)
  6. 在 y 中存储 42

让我们将其映射到您的第一个示例:

for (let i of [100,300,200])
   output1 = output1 + (await sleep(i));
Run Code Online (Sandbox Code Playgroud)
  1. 记住当前 output1 值 (0)
  2. 启动第一个异步“睡眠”任务并等待其完成
  3. 记住当前 output1 值 (0)
  4. 启动第二个异步睡眠任务并等待其完成
  5. 记住当前 output1 值 (0)
  6. 启动第三个异步睡眠任务并等待其完成
  7. 第一个睡眠任务完成 (100)
  8. 将当前 output1 值和任务结果相加 (0 + 100)
  9. 将结果存储在 output1 (100) 中
  10. 第三个睡眠任务完成 (200)
  11. 将当前 output1 值和任务结果相加 (0 + 200)
  12. 将结果存储在 output1 (200)
  13. 第二个睡眠任务完成 (300)
  14. 将当前 output1 值和任务结果相加 (0 + 300)
  15. 将结果存储在 output1 (300)
  16. 的最终值为output1300

现在比较第二个例子。评估顺序变为:

for (let i of [100,300,200]) {
  const res = await sleep(sec);
  output2 = output2 + res;
}
Run Code Online (Sandbox Code Playgroud)
  1. 启动第一个异步“睡眠”任务并等待其完成
  2. 启动第二个异步睡眠任务并等待其完成
  3. 启动第三个异步睡眠任务并等待其完成
  4. 第一个睡眠任务完成 (100)
  5. 记住当前 output2 值 (0)
  6. 将当前 output2 值和任务结果相加 (0 + 100)
  7. 将结果存储在 output2 (100)
  8. 第三个睡眠任务完成 (200)
  9. 记住当前 output2 值 (100)
  10. 将当前 output2 值和任务结果相加 (100 + 200)
  11. 将结果存储在 output2 (300)
  12. 第二个睡眠任务完成 (300)
  13. 记住当前 output2 值 (300)
  14. 将当前 output2 值和任务结果相加 (300 + 300)
  15. 将结果存储在 output2 (600)
  16. 的最终值为output2 600

  • 附带说明:`output1 = (await sleep(sec)) + output1;` 应该像 output2 一样工作。表达式 `x + y` 从左到右求值,因此它首先等待 `await sleep(sec)`,然后获取 `output1` 值。对我来说,我认为它是“从左到右评估表达式”而不是“缓存”,但效果是一样的 (2认同)
  • 不,我所说的缓存是“评估”/“读取变量指向的当前值”。通过分步执行计划查看我的编辑 (2认同)