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

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

问题描述

在以下情况下为什么会有不同的结果?第一个示例正常工作,返回三个元素["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. 他们返回了一个承诺.

但是第一点实际上是错误的.异步函数将同步运行,直到遇到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.

基本上,任何运行 now 的代码都在事件循环的当前执行范围之内.任何允诺将最早在 next 迭代中得到解决.如果有多个Promises,那么您可能需要等待几次迭代.无论哪种情况,都会在以后发生.

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.

为了更加清楚,这是解释: 代码之前 await将与它引用的任何内容的 current 值同步完成,而代码之后 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) 

表示首先要等待该值,然后按分辨率获取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))

表示将首先提取 values,然后 then 函数将暂停以等待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应该在有 个要等待的多个承诺时使用.
  3. values是异步操作之间的共享变量,异步操作之间的共享变量可能会遇到访问公共资源的所有异步代码的常见问题.读取或写入操作可能会将资源从实际的不同状态更改为当前状态.这是在第二个版本的代码中发生的情况,每次写入都使用 initial 而不是目前的状态.
  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来创建一个Promises数组.
  2. 使用await Promise.all等待以上所有问题解决.
  3. 已解决承诺后,将结果同步合并到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天全站免登陆