【问题标题】:map() function with async/await带有 async/await 的 map() 函数
【发布时间】:2019-03-18 15:54:24
【问题描述】:

关于 async/await 在 javascript map 函数中的行为方式已经发布了相当多的主题,但是,下面两个示例中的详细说明会很好:

  const resultsPromises = myArray.map(async number => {
    return await getResult(number);
  });
  const resultsPromises = myArray.map(number => {
    return getResult(number);
  });

已编辑:这当然是一个虚构的案例,所以刚刚开始辩论,为什么、如何以及何时应该 map 函数等待 await 关键字。解决方案如何修改这个例子,调用 Promise.all() 不是这个问题的目的。
getResult 是一个异步函数

【问题讨论】:

  • 将此与Promise.all结合起来。
  • @Alnitak 我同意这是一种气味,但是当我需要处理服务节流或有目的的、顺序的、异步的时,我会使用它。
  • @arielb - 不正确 - async 函数 总是 返回一个 Promise,所以所发生的只是它正在等待解决 getResult(),并且然后将其作为 new Promise. 的值传递
  • @TylerRoper 我相信你错了。 .map 不会等待内部的await 完成,所以效果是它们都会并行运行。
  • @marko424 这很简单 - 它没有

标签: javascript asynchronous promise


【解决方案1】:

其他答案已经很好地涵盖了您的示例行为的细节,但我想尝试更简洁地说明它。

const resultsPromises = myArray.map(async number => {
  return await getResult(number);
});
const resultsPromises = myArray.map(number => {
  return getResult(number);
});
  1. Array.prototype.map 同步循环遍历数组并将每个元素转换为其回调的返回值。

  2. 两个例子都返回Promise

    • async 函数总是返回Promise

    • getResult 返回一个Promise

    • 因此,如果没有错误,您可以在伪代码中将它们都视为:

const resultsPromises = myArray.map(/* map each element to a Promise */);
  1. 作为zero298 statedalnitak demonstrated,这非常快速(同步)按顺序启动每个承诺;但是,由于它们是并行运行的,因此每个 Promise 都会在它们认为合适的时候解决/拒绝,并且可能不会按顺序解决(履行或拒绝)。

  2. 要么并行运行 Promise 并使用 Promise.all 收集结果,要么使用 for * loopArray.prototype.reduce 按顺序运行它们。

或者,您可以使用我维护的chainable asynchronous JavaScript methods 的第三方模块来清理内容,并且——也许——使代码符合您对async map 操作可能如何工作的直觉:

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

const getResult = async n => {
  await delay(Math.random() * 1000);
  console.log(n);
  return n;
};

(async () => {
  console.log('parallel:');
  await AsyncAF([1, 2, 3]).map(getResult).then(console.log);
  
  console.log('sequential:');
  await AsyncAF([1, 2, 3]).series.map(getResult).then(console.log)
})();
<script src="https://unpkg.com/async-af@7.0.12/index.js"></script>

【讨论】:

  • return await 在异步调用中是无用且低效的,因为异步函数将以任何方式返回一个承诺
【解决方案2】:

Array.prototype.map() 是一个转换数组的函数。它将一个数组映射到另一个数组。其函数签名中最重要的部分是回调。对 Array 中的每个项目调用回调,回调返回的是放入 map 返回的新 Array 中的内容。

它不会对返回的内容做任何特别的事情。它不会在项目上调用.then(),它不会await 任何东西。它同步转换数据。

这意味着如果回调返回一个Promise(所有async 函数都会这样做),所有的promise 都将是“热的”并且并行运行。

在您的示例中,如果 getResult() 返回 Promise 或者本身是异步的,那么您的实现之间并没有真正的区别。 resultsPromises 将由 Promises 填充,可能尚未解决,也可能尚未解决。

如果您想等待一切完成后再继续,您需要使用Promise.all()

此外,如果您只希望一次运行 1 个getResults(),请使用常规的for 循环和循环内的await

【讨论】:

    【解决方案3】:

    async/await 在您想要通过删除 .then() 回调来扁平化代码或想要隐式返回 Promise 时非常有用:

    const delay = n => new Promise(res => setTimeout(res, n));
    
    async function test1() {
      await delay(200);
      // do something usefull here
      console.log('hello 1');
    }
    
    async function test2() {
      return 'hello 2'; // this returned value will be wrapped in a Promise
    }
    
    test1();
    test2().then(console.log);

    但是,在您的情况下,您没有使用 await 替换 .then(),也没有使用它来返回隐式 Promise,因为您的函数已经返回了 Promise。所以它们不是必需的。

    所有 Promises 的并行执行

    如果你想并行运行所有 Promises,我建议简单地返回 getResultmap() 的结果并生成一个 Promises 数组。 Promise 将按顺序启动,但最终将并行运行。

    const resultsPromises = indicators.map(getResult);
    

    然后你可以等待所有的承诺,并使用Promise.all()得到解决的结果:

    const data = [1, 2, 3];
    
    const getResult = x => new Promise(res => {
      return setTimeout(() => {
        console.log(x);
        res(x);
      }, Math.random() * 1000)
    });
    
    Promise.all(data.map(getResult)).then(console.log);

    Promises 的顺序执行

    但是,如果你想按顺序运行每个 Promise 并等待前一个 Promise 解决后再运行下一个,那么你可以像这样使用reduce()async/await

    const data = [1, 2, 3];
    
    const getResult = x => new Promise(res => {
      return setTimeout(() => {
        console.log(x);
        res(x);
      }, Math.random() * 1000)
    });
    
    data.reduce(async (previous, x) => {
      const result = await previous;
      return [...result, await getResult(x)];
    }, Promise.resolve([])).then(console.log);

    【讨论】:

      【解决方案4】:

      如果第一个代码 sn-p 的意图是有一个 .map 调用,等待所有 Promise 在返回之前得到解决(并且让这些回调按顺序运行),恐怕它不会那样工作。 .map 函数不知道如何使用 async 函数来做到这一点。

      这可以用下面的代码来演示:

      const array = [ 1, 2, 3, 4, 5 ];
            
      function getResult(n)
      {
          console.log('starting ' + n);
      
          return new Promise(resolve => {
              setTimeout(() => {
                  console.log('finished ' + n);
                  resolve(n);
              }, 1000 * (Math.random(5) + 1));
          });
      }
      
      let promises = array.map(async (n) => {
          return await getResult(n);
      });
      
      console.log('map finished');
      
      Promise.all(promises).then(console.log);

      您会看到.map 调用在任何异步操作完成之前立即完成。

      【讨论】:

        【解决方案5】:

        如果getResult 总是返回一个承诺并且从不抛出错误,那么两者的行为将相同。

        一些promise返回函数可能会在promise返回之前抛出错误,在这种情况下,将对getResult的调用包装在异步函数中会将抛出的错误转换为被拒绝的promise,这可能很有用。

        正如许多 cmets 所述,您永远不需要 return await - 它相当于在承诺链的末尾添加 .then(result=>result) - 它(大部分)无害但没有必要。只需使用return

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-04-15
          • 2017-08-26
          • 2021-08-06
          • 2018-01-31
          • 1970-01-01
          • 2017-11-07
          • 2019-10-04
          • 1970-01-01
          相关资源
          最近更新 更多