当 await 表达式是 concat() 的参数时,为什么 async/await 有不同的输出?
Why async/await have different output when await expression is a argument of concat()?
我对下面的示例感到困惑。
我理解为什么 output2
是 [1000,2000,3000]
因为闭包,这就是为什么 map()
中的所有异步函数更新同一个数组 output2
。 (如有错误请指正。)
但是,我不明白为什么output1
是[3000]
。
我可以知道为什么 run1
不像 run2
那样吗?你能告诉我区别吗?
"use strict";
function sleep(ms) {
return new Promise(resolve =>
setTimeout(() => {
resolve(ms);
}, ms)
);
}
const seconds = [1000, 3000, 2000];
let output1 = [];
let output2 = [];
(async function run1() {
await Promise.all(
seconds.map(async sec => {
output1 = output1.concat([await sleep(sec)]);
})
);
console.log({ output1 });
})();
(async function run2() {
await Promise.all(
seconds.map(async sec => {
const res = await sleep(sec);
output2 = output2.concat([res]);
})
);
console.log({ output2 });
})();
我在第一个示例之间添加了一行。猜猜这将帮助您了解原因。
"use strict";
function sleep(ms) {
return new Promise(resolve =>
setTimeout(() => {
resolve(ms);
}, ms)
);
}
const seconds = [1000, 3000, 2000];
let output1 = [];
(async function run1() {
await Promise.all(
seconds.map(async sec => {
const dummy = output1;
console.log(`dummy_${sec}`, dummy);
output1 = dummy.concat([await sleep(sec)]);
})
);
console.log({ output1 });
})();
更新:
以下是您使用生成器 polyfill 转译为 ES5 代码的原始代码。注意转译器如何在 output1.concat([ /* pause here */ ])
调用之间暂停。
诀窍是在 yield 之前将 .concat
就地绑定到 output1
,然后等待继续。
在这个绑定发生的那一刻,output1 == []
,这就是我想通过重新分配给 dummy
变量并在前面的代码中打印它来强调的。
var seconds = [1000, 3000, 2000];
var output1 = [];
(function run1() {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, Promise.all(seconds.map(function (sec) { return __awaiter(_this, void 0, void 0, function () {
var _a, _b;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
_b = (_a = output1).concat;
return [4 /*yield*/, sleep(sec)];
case 1:
output1 = _b.apply(_a, [[_c.sent()]]);
return [2 /*return*/];
}
});
}); }))];
case 1:
_a.sent();
console.log({ output1: output1 });
return [2 /*return*/];
}
});
});
})();
查看声明
output1 = output1.concat([await sleep(sec)]);
左侧output1
是一个变量标识符,用于提供存储右侧计算结果的位置。变量的绑定不会改变,它始终提供变量值的位置。
右侧 output1
是一个值 - 从变量名称提供的位置检索的值。
现在,如果 JavaScript 引擎在继续评估之前检索到 output1
的值,所有三个映射函数调用
- 检索对存储在
output
、 中的空数组的引用
- 等待计时器承诺并将
output1
设置为新值 是 return 从 concat
方法编辑的数组。
因此每个 map 操作将一个包含计时器值的数组连接到一个空数组,并将结果存储在 output1
覆盖之前等待的操作的结果.
这解释了为什么当 Promise.all
结算时您只能看到存储在 output1
中的最后一个数组。我还将收回上面“如果 JavaScript 引擎...”的措辞。 JavaScript 引擎确实在 await
:
之前获得了 output1
的值
function sleep(ms) {
return new Promise(resolve =>
setTimeout(() => {
console.log( output1.length);
resolve(ms);
}, ms)
);
}
const seconds = [1000, 3000, 2000];
let output1 = [];
let output2 = [];
(async function run1() {
await Promise.all(
seconds.map(async sec => {
output1 = output1.concat([await sleep(sec)]);
//output1.push(await sleep(sec));
console.log(output1[0]);
})
);
console.log({ output1 });
})();
let count = 6;
let timer = setInterval( ()=> {
console.log(output1[0])
if(--count <=0 ) {
clearInterval( timer);
}
}, 500);
要弄清楚为什么第二种方法 (run2
) 有效(这与闭包的存在无关):
.map
方法同步调用map函数同步return一个promise无需等待用于计时器承诺履行。
在第二个版本中,
seconds.map(async sec => {
const res = await sleep(sec);
output2 = output2.concat([res]);
}
const res = await sleep( sec)
行保存执行上下文并等待 sleep
承诺履行。当承诺完成时,await
恢复保存的上下文并将承诺值存储在 res
中。下一行
ouput2 = output2.concat([res]);
在 计时器到期后执行 并且在右侧将在执行该行时加载 output2
current 的值,如果由先前的计时器到期更新发生了一个。
将此与 run1
进行对比,其中 JavaScript 引擎在开始计算赋值运算符右侧的表达式时基本上缓存了 ouput1
的值并使用如代码片段中所示,所有迭代的空数组值相同。
*副本的 表示加法运算的左手操作数是在右手操作数被 return 编辑 await
之前从存储中检索的。在 run1
的情况下,我们看到调用方法 的对象(output1
的值)在参数值之前被检索用于调用的方法已经确定。正如对链接答案的评论中所述,这是一个相当“隐藏的陷阱”。
我对下面的示例感到困惑。
我理解为什么 output2
是 [1000,2000,3000]
因为闭包,这就是为什么 map()
中的所有异步函数更新同一个数组 output2
。 (如有错误请指正。)
但是,我不明白为什么output1
是[3000]
。
我可以知道为什么 run1
不像 run2
那样吗?你能告诉我区别吗?
"use strict";
function sleep(ms) {
return new Promise(resolve =>
setTimeout(() => {
resolve(ms);
}, ms)
);
}
const seconds = [1000, 3000, 2000];
let output1 = [];
let output2 = [];
(async function run1() {
await Promise.all(
seconds.map(async sec => {
output1 = output1.concat([await sleep(sec)]);
})
);
console.log({ output1 });
})();
(async function run2() {
await Promise.all(
seconds.map(async sec => {
const res = await sleep(sec);
output2 = output2.concat([res]);
})
);
console.log({ output2 });
})();
我在第一个示例之间添加了一行。猜猜这将帮助您了解原因。
"use strict";
function sleep(ms) {
return new Promise(resolve =>
setTimeout(() => {
resolve(ms);
}, ms)
);
}
const seconds = [1000, 3000, 2000];
let output1 = [];
(async function run1() {
await Promise.all(
seconds.map(async sec => {
const dummy = output1;
console.log(`dummy_${sec}`, dummy);
output1 = dummy.concat([await sleep(sec)]);
})
);
console.log({ output1 });
})();
更新:
以下是您使用生成器 polyfill 转译为 ES5 代码的原始代码。注意转译器如何在 output1.concat([ /* pause here */ ])
调用之间暂停。
诀窍是在 yield 之前将 .concat
就地绑定到 output1
,然后等待继续。
在这个绑定发生的那一刻,output1 == []
,这就是我想通过重新分配给 dummy
变量并在前面的代码中打印它来强调的。
var seconds = [1000, 3000, 2000];
var output1 = [];
(function run1() {
return __awaiter(this, void 0, void 0, function () {
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, Promise.all(seconds.map(function (sec) { return __awaiter(_this, void 0, void 0, function () {
var _a, _b;
return __generator(this, function (_c) {
switch (_c.label) {
case 0:
_b = (_a = output1).concat;
return [4 /*yield*/, sleep(sec)];
case 1:
output1 = _b.apply(_a, [[_c.sent()]]);
return [2 /*return*/];
}
});
}); }))];
case 1:
_a.sent();
console.log({ output1: output1 });
return [2 /*return*/];
}
});
});
})();
查看声明
output1 = output1.concat([await sleep(sec)]);
左侧output1
是一个变量标识符,用于提供存储右侧计算结果的位置。变量的绑定不会改变,它始终提供变量值的位置。
右侧 output1
是一个值 - 从变量名称提供的位置检索的值。
现在,如果 JavaScript 引擎在继续评估之前检索到 output1
的值,所有三个映射函数调用
- 检索对存储在
output
、 中的空数组的引用
- 等待计时器承诺并将
output1
设置为新值 是 return 从concat
方法编辑的数组。
因此每个 map 操作将一个包含计时器值的数组连接到一个空数组,并将结果存储在 output1
覆盖之前等待的操作的结果.
这解释了为什么当 Promise.all
结算时您只能看到存储在 output1
中的最后一个数组。我还将收回上面“如果 JavaScript 引擎...”的措辞。 JavaScript 引擎确实在 await
:
output1
的值
function sleep(ms) {
return new Promise(resolve =>
setTimeout(() => {
console.log( output1.length);
resolve(ms);
}, ms)
);
}
const seconds = [1000, 3000, 2000];
let output1 = [];
let output2 = [];
(async function run1() {
await Promise.all(
seconds.map(async sec => {
output1 = output1.concat([await sleep(sec)]);
//output1.push(await sleep(sec));
console.log(output1[0]);
})
);
console.log({ output1 });
})();
let count = 6;
let timer = setInterval( ()=> {
console.log(output1[0])
if(--count <=0 ) {
clearInterval( timer);
}
}, 500);
要弄清楚为什么第二种方法 (run2
) 有效(这与闭包的存在无关):
.map
方法同步调用map函数同步return一个promise无需等待用于计时器承诺履行。
在第二个版本中,
seconds.map(async sec => {
const res = await sleep(sec);
output2 = output2.concat([res]);
}
const res = await sleep( sec)
行保存执行上下文并等待 sleep
承诺履行。当承诺完成时,await
恢复保存的上下文并将承诺值存储在 res
中。下一行
ouput2 = output2.concat([res]);
在 计时器到期后执行 并且在右侧将在执行该行时加载 output2
current 的值,如果由先前的计时器到期更新发生了一个。
将此与 run1
进行对比,其中 JavaScript 引擎在开始计算赋值运算符右侧的表达式时基本上缓存了 ouput1
的值并使用如代码片段中所示,所有迭代的空数组值相同。
*副本的 await
之前从存储中检索的。在 run1
的情况下,我们看到调用方法 的对象(output1
的值)在参数值之前被检索用于调用的方法已经确定。正如对链接答案的评论中所述,这是一个相当“隐藏的陷阱”。