Riw*_*iwa 1 javascript arrays variables promise async-await
为什么以下情况会出现不同的结果?第一个示例工作正常,返回一个包含三个元素的数组["qwe", "rty", "asd"]
。第二个示例仅返回最后一个元素["asd"]
。请解释一下它是如何工作的?为什么会发生这种行为?
在第一个示例中,通过中间变量进行工作awaitResult
。
class XXX {
constructor() {
this.storage = {1: ['qwe'], 2: ['rty'], 3: ['asd']}
}
async getValue(key) {
return this.storage[key];
}
async logValues() {
let keys = [1, 2, 3]
let values = []
// ----- First version -----
await Promise.all(
keys.map(
async key => {
let awaitResult = await this.getValue(key)
values = values.concat(awaitResult)
}
)
);
console.log(values)
}
}
let xxx = new XXX()
xxx.logValues()
Run Code Online (Sandbox Code Playgroud)
在第二个例子中,没有awaitResult
.
class XXX {
constructor() {
this.storage = {1: ['qwe'], 2: ['rty'], 3: ['asd']}
}
async getValue(key) {
return this.storage[key];
}
async logValues() {
let keys = [1, 2, 3]
let values = []
// ----- Second version -----
await Promise.all(
keys.map(
async key => values = values.concat(await this.getValue(key)),
)
);
console.log(values)
}
}
let xxx = new XXX()
xxx.logValues()
Run Code Online (Sandbox Code Playgroud)
乔纳斯·威尔姆斯的回答绝对正确。我只是想对其进行一些澄清,因为有两个关键的事情需要理解:
我认为,这是最重要的事情。事情是这样的 - 异步函数知识 101:
但第一点实际上是错误的。异步函数将同步运行,直到遇到await
关键字后跟 Promise,然后暂停,等待 Promise 解析并继续:
function getValue() {
return 42;
}
async function notReallyAsync() {
console.log("-- function start --");
const result = getValue();
console.log("-- function end --");
return result;
}
console.log("- script start -");
notReallyAsync()
.then(res => console.log(res));
console.log("- script end -");
Run Code Online (Sandbox Code Playgroud)
因此,在调用时将运行完成,因为其中notReallyAsync
没有。await
它仍然返回一个 Promise,该 Promise 只会被放入事件队列并在事件循环的下一次迭代中解决。
但是,如果它确实有await
,则该函数将在该点暂停,并且后的await
任何代码将仅在 Promise 解决后运行:
function getAsyncValue() {
return new Promise(resolve => resolve(42));
}
async function moreAsync() {
console.log("-- function start --");
const result = await getAsyncValue();
console.log("-- function end --");
return result;
}
console.log("- script start -");
moreAsync()
.then(res => console.log(res));
console.log("- script end -");
Run Code Online (Sandbox Code Playgroud)
所以,这绝对是理解正在发生的事情的关键。第二部分实际上只是第一部分的结果
是的,我之前提到过,但仍然 - 承诺解析是作为事件循环执行的一部分发生的。网上可能有更好的资源,但我写了一个简单的(我希望)概述它是如何工作的,作为我的答案的一部分。如果您在那里了解了事件循环的基本概念 - 很好,这就是您所需要的一切,基础知识。
本质上,现在运行的任何代码都在事件循环的当前执行范围内。任何承诺都将最早在下一次迭代中得到解决。如果有多个 Promise,那么您可能需要等待几次迭代。不管怎样,都是以后的事了。
为了更清楚起见,解释如下:之前的 代码将与其引用的任何内容的当前await
值同步完成,而之后的代码将在下一个事件循环中发生: await
let awaitResult = await this.getValue(key)
values = values.concat(awaitResult)
Run Code Online (Sandbox Code Playgroud)
意味着将首先等待该值,然后在解析时values
获取该值并将awaitResult
其连接到该值。如果我们表示按顺序发生的事情,您会得到如下结果:
let values = [];
//function 1:
let key1 = 1;
let awaitResult1;
awaitResult1 = await this.getValue(key1); //pause function 1 wait until it's resolved
//function 2:
key2 = 2;
let awaitResult2;
awaitResult2 = await this.getValue(key2); //pause function 2 and wait until it's resolved
//function 3:
key3 = 3;
let awaitResult3;
awaitResult3 = await this.getValue(key3); //pause function 3 and wait until it's resolved
//...event loop completes...
//...next event loop starts
//the Promise in function 1 is resolved, so the function is unpaused
awaitResult1 = ['qwe'];
values = values.concat(awaitResult1);
//...event loop completes...
//...next event loop starts
//the Promise in function 2 is resolved, so the function is unpaused
awaitResult2 = ['rty'];
values = values.concat(awaitResult2);
//...event loop completes...
//...next event loop starts
//the Promise in function 3 is resolved, so the function is unpaused
awaitResult3 = ['asd'];
values = values.concat(awaitResult3);
Run Code Online (Sandbox Code Playgroud)
因此,您将在一个数组中正确添加所有值。
然而,以下情况:
values = values.concat(await this.getValue(key))
Run Code Online (Sandbox Code Playgroud)
意味着首先 values
将被获取,然后函数暂停以等待 的解析this.getValue(key)
。由于values
总是在对其进行任何修改之前获取,因此该值始终是一个空数组(起始值),因此这相当于以下代码:
let values = [];
//function 1:
values = [].concat(await this.getValue(1)); //pause function 1 and wait until it's resolved
// ^^ what `values` is always equal during this loop
//function 2:
values = [].concat(await this.getValue(2)); //pause function 2 and wait until it's resolved
// ^^ what `values` is always equal to at this point in time
//function 3:
values = [].concat(await this.getValue(3)); //pause function 3 and wait until it's resolved
// ^^ what `values` is always equal to at this point in time
//...event loop completes...
//...next event loop starts
//the Promise in function 1 is resolved, so the function is unpaused
values = [].concat(['qwe']);
//...event loop completes...
//...next event loop starts
//the Promise in function 2 is resolved, so the function is unpaused
values = [].concat(['rty']);
//...event loop completes...
//...next event loop starts
//the Promise in function 3 is resolved, so the function is unpaused
values = [].concat(['asd']);
Run Code Online (Sandbox Code Playgroud)
底线 - 的位置await
确实会影响代码的运行方式,从而影响其语义。
这是一个相当冗长的解释,但问题的实际根源是这段代码没有正确编写:
.map
简单的循环操作是不好的做法。它应该用于执行映射操作 - 将数组的每个元素 1:1 转换为另一个数组。这里,.map
只是一个循环。await Promise.all
当有多个 Promise 等待时应该使用。values
是异步操作之间的共享变量,它可能会遇到访问公共资源的所有异步代码的常见问题 - “脏”读取或写入可能会将资源从与实际状态不同的状态更改。这就是第二个中发生的情况每次写入都使用初始值 values
而不是当前持有的代码版本。适当地使用这些我们得到:
.map
做出一系列 Promise。await Promise.all
等待以上所有问题都得到解决。values
同步合并。class XXX {
constructor() {
this.storage = {1: ['qwe'], 2: ['rty'], 3: ['asd']}
}
async getValue(key) {
console.log()
return this.storage[key];
}
async logValues() {
console.log("start")
let keys = [1, 2, 3]
let results = await Promise.all( //2. await all promises
keys.map(key => this.getValue(key)) //1. convert to promises
);
let values = results.reduce((acc, result) => acc.concat(result), []); //3. reduce and concat the results
console.log(values);
}
}
let xxx = new XXX()
xxx.logValues()
Run Code Online (Sandbox Code Playgroud)
这也可以在运行时折叠到 Promise API 中Promise.all().then
:
class XXX {
constructor() {
this.storage = {1: ['qwe'], 2: ['rty'], 3: ['asd']}
}
async getValue(key) {
console.log()
return this.storage[key];
}
async logValues() {
console.log("start")
let keys = [1, 2, 3]
let values = await Promise.all( //2. await all promises
keys.map(key => this.getValue(key)) //1. convert to promises
)
.then(results => results.reduce((acc, result) => acc.concat(result), []));//3. reduce and concat the results
console.log(values);
}
}
let xxx = new XXX()
xxx.logValues()
Run Code Online (Sandbox Code Playgroud)