将临时变量分配给变量时异步函数的不同行为 [英] Different behavior of async functions when assigning temporary to variable

查看:14
本文介绍了将临时变量分配给变量时异步函数的不同行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为什么在以下情况下会有不同的结果?第一个示例工作正常,返回一个包含三个元素的数组 ["qwe", "rty", "asd"].第二个例子只返回最后一个元素 ["asd"].请解释它是如何工作的?为什么会发生这种行为?

Why a different result in the following cases? The first example works correctly, returns an array of three elements ["qwe", "rty", "asd"]. Second example return only last element ["asd"]. Please, explain how it work? Why is this behavior happening?

在第一个例子中处理中间变量 awaitResult.

In the first example working through intermediate variable 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()

在没有 awaitResult 的第二个例子中.

In the second example working without 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()

推荐答案

Jonas Wilms 的回答是完全正确的.我只是想对其进行一些澄清,因为有两个关键的事情需要理解:

The answer from Jonas Wilms is absolutely correct. I just want to expand upon it with some clarification, as there are two key things one needs to understand:

我认为这是最重要的事情.这是事情 - 异步函数的知识 101:

This, I think, is the most important thing. Here is the thing - knowledge of async functions 101:

  1. 他们将稍后执行.
  2. 他们返回一个 Promise.

但第一点实际上是错误的.异步函数将同步运行,直到遇到 await 关键字后跟一个 Promise,然后然后暂停,等待 Promise 解决并继续:

But point one is actually wrong. Async functions will run synchronously until they encounter an await keyword followed by a Promise, and then pause, wait until the Promise is resolved and continue:

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 -");

因此,notReallyAsync 将在调用时运行到完成,因为其中没有 await.它仍然返回一个 Promise,该 Promise 只会放入事件队列并在事件循环的下一次迭代中解析.

So, notReallyAsync will run to completion when called, since there is no await in it. It still returns a Promise which will only be put on the event queue and resolved on a next iteration of the event loop.

然而,如果它确实await,那么函数在那个点暂停和任何代码之后await 只会在 Promise 解决后运行:

However, if it does have await, then the function pauses at that point and any code after the await will only be run after the Promise is resolved:

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 -");

所以,这绝对是了解正在发生的事情的关键.第二部分实际上只是第一部分的结果

So, this is absolutely the key to understanding what's happening. The second part is really just a consequence of this first part

是的,我之前提到过,但仍然 - 承诺解析作为事件循环执行的一部分发生.网上可能有更好的资源,但我写了一个简单的(我希望)概述它是如何工作的作为我在这里回答的一部分.如果您了解事件循环的基本概念 - 很好,这就是您所需要的全部基础知识.

Yes, I mentioned it before but still - promise resolution happens as part of the event loop execution. There are probably better resources online but I wrote a simple (I hope) outline of how it works as part of my answer here. If you get the basic idea of the event loop there - good, that's all you need, the basics.

本质上,任何现在运行的代码都在事件循环的当前执行中.任何承诺都将最早在 次迭代中得到解决.如果有多个 Promise,那么您可能需要等待几次迭代.无论如何,它会稍后发生.

Essentially, any code that runs now is within the current execution of the event loop. Any promise will be resolved the next iteration the earliest. If there are multiple Promises, then you might need to wait few iterations. Whatever the case, it happens later.

为了更清楚,这里是解释:代码 before await 将与其引用的任何 current 值同步完成,而代码 after await 将在下一个事件循环中发生:

To make it more clear, here is the explanation: Code before await will be completed synchronously with the current values of anything it references while code after await will happen the next event loop:

let awaitResult = await this.getValue(key)
values = values.concat(awaitResult) 

意味着该值将被first等待,然后在解析时values将被获取并且awaitResult将被连接到它.如果我们表示按顺序发生的事情,你会得到类似的结果:

means that the value will be awaited first, then upon resolution values will be fetched and awaitResult will be concatenated to it. If we represent what happens in sequence, you would get something like:

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

因此,您会将所有值正确添加到一个数组中.

So, you would get all of the values added correctly together in one array.

但是,以下内容:

values = values.concat(await this.getValue(key))

意味着 first values 将被获取,然后 函数暂停等待 this.getValue(key) 的解析.由于 values 将始终在对它进行任何修改之前 被获取,因此该值始终是一个空数组(起始值),因此这等效于以下内容代码:

means that first values will be fetched and then the function pauses to await the resolution of this.getValue(key). Since values will always be fetched before any modifications have been made to it, then the value is always an empty array (the starting value), so this is equivalent to the following code:

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']);

底线 - await 确实会影响代码的运行方式,从而影响其语义.

Bottom line - the position of await does affect how the code runs and can thus its semantics.

这是一个相当冗长的解释,但问题的真正根源在于这段代码没有正确编写:

This was a pretty lengthy explanation but the actual root of the problem is that this code is not written correctly:

  1. 为简单的循环操作运行 .map 是不好的做法.它应该用于执行 mapping 操作 - 将数组的每个元素 1:1 转换为另一个数组.在这里,.map 只是一个循环.
  2. await Promise.all 应该在 多个 Promise 等待时使用.
  3. values 是异步操作之间的共享变量,它可能会遇到访问公共资源的所有异步代码的常见问题 - 脏"读取或写入可以将资源从不同状态更改为与实际所处的状态不同.这就是代码的第二个版本中发生的情况,其中每次写入都使用 initial values 而不是它当前所拥有的.
  1. Running .map for a simple looping operation is bad practice. It should be used to do a mapping operation - a 1:1 transformation of each element of the array to another array. Here, .map is merely a loop.
  2. await Promise.all should be used when there are multiple Promises to await.
  3. values is a shared variable between async operations which can run into common problems with all asynchronous code that accesses a common resource - "dirty" reads or writes can change the resource from a different state than it actually is in. This is what happens in the second version of the code where each write uses the initial values instead of what it currently holds.

适当地使用这些,我们得到:

Using these appropriately we get:

  1. 使用 .map 制作一个 Promise 数组.
  2. 使用 await Promise.all 等待以上所有问题都解决.
  3. 当 Promises 得到解决时,将结果组合成 values 同步.
  1. Use .map to make an array of Promises.
  2. Use await Promise.all to wait until all of the above are resolved.
  3. Combine the results into values synchronously when the Promises have been resolved.

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()

这也可以在运行 Promise.all().then 时折叠到 Promise API 中:

This can also be folded into the Promise API as running 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()

这篇关于将临时变量分配给变量时异步函数的不同行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆