【问题标题】:Use async await with Array.map将异步等待与 Array.map 一起使用
【发布时间】:2017-03-01 14:39:28
【问题描述】:

给定以下代码:

var arr = [1,2,3,4,5];

var results: number[] = await arr.map(async (item): Promise<number> => {
        await callAsynchronousOperation(item);
        return item + 1;
    });

会产生以下错误:

TS2322:类型“Promise[]”不可分配给类型“number[]”。 类型 'Promise 不可分配给类型 'number'。

我该如何解决?我怎样才能让async awaitArray.map 一起工作?

【问题讨论】:

  • 你为什么要把同步操作变成异步操作? arr.map() 是同步的,不返回承诺。
  • 您不能将异步操作发送到一个函数,例如 map,它需要一个同步操作,并期望它能够工作。
  • @jfriend00 我在内部函数中有很多等待语句。它实际上是一个很长的函数,我只是简化了它以使其可读。我现在添加了一个等待调用,以更清楚地说明为什么它应该是异步的。
  • 你需要等待返回承诺的东西,而不是返回数组的东西。
  • 要意识到的一件有用的事情是,每次将函数标记为async 时,都会使该函数返回一个承诺。所以当然,async 的映射会返回一组 promise :)

标签: javascript typescript promise async-await ecmascript-2017


【解决方案1】:

这可能对某人有所帮助。

const APISimulator = (v) => new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve({ data: v });
    }, v * 100);
});

const arr = [7, 6, 5, 1, 2, 3];

const res = () => arr.reduce(async (memo, v, i) => {
    const results = await memo;
    console.log(`proccessing item-${i + 1} :`, v)
    await APISimulator(v);
    console.log(`completed proccessing-${i + 1} :`, v)

    return [...results, v];
}, []);

res().then(proccessed => console.log(proccessed))

【讨论】:

    【解决方案2】:

    下面的解决方案可以同时正确使用 async await 和 Array.map。并行、异步处理数组的所有元素并保持顺序:

    const arr = [1, 2, 3, 4, 5, 6, 7, 8];
    const randomDelay = () => new Promise(resolve => setTimeout(resolve, Math.random() * 1000));
    
    const calc = async n => {
      await randomDelay();
      return n * 2;
    };
    
    const asyncFunc = async () => {
      const unresolvedPromises = arr.map(n => calc(n));
      const results = await Promise.all(unresolvedPromises);
    };
    
    asyncFunc();
    

    还有codepen

    请注意,我们只“等待” Promise.all。我们多次调用 calc 而没有“等待”,我们会立即收集一系列未解决的 Promise。然后 Promise.all 等待所有的解析,并按顺序返回一个包含解析值的数组。

    【讨论】:

    • 在使用 Javascript 的这些年里,由于 js 的异步特性,我一直在映射数组时遇到问题,而不会弄乱顺序。我使用了promised.all,但从未想过它会按原样解决它们。这个答案从字面上改变了我现在写地图和其他东西的方式。上帝保佑。
    • 感谢这个解决方案,我发现它比其他提供的解决方案更有说服力和可读性。
    【解决方案3】:

    这里的问题是你试图await 一个承诺数组而不是一个承诺。这不符合您的预期。

    当传递给await 的对象不是Promise 时,await 只是立即按原样返回值,而不是尝试解析它。因此,由于您在此处传递 await 一个(Promise 对象的)数组而不是 Promise,所以 await 返回的值就是该数组,它的类型为 Promise&lt;number&gt;[]

    您可能想要做的是在map 返回的数组上调用Promise.all,以便在await 之前将其转换为单个Promise。

    根据MDN docs for Promise.all

    Promise.all(iterable) 方法返回一个可以解析的承诺 当可迭代参数中的所有承诺都已解决时,或 以第一个通过的 promise 的原因拒绝。

    所以在你的情况下:

    var arr = [1, 2, 3, 4, 5];
    
    var results: number[] = await Promise.all(arr.map(async (item): Promise<number> => {
        await callAsynchronousOperation(item);
        return item + 1;
    }));
    

    这将解决您在此处遇到的特定错误。

    根据你想要做什么,你也可以考虑使用Promise.allSettledPromise.anyPromise.race而不是Promise.all,尽管在大多数情况下(几乎可以肯定包括这个)@ 987654340@ 将是您想要的。

    【讨论】:

    • : 冒号是什么意思?
    • @DanielPendergast 用于 TypeScript 中的类型注释。
    • 在异步映射函数中调用callAsynchronousOperation(item);和不使用await有什么区别?
    • @nerdizzle 这听起来像是另一个问题的好人选。不过基本上,使用await,该函数将等待异步操作完成(或失败)再继续,否则它将立即继续而无需等待。
    • @Ajedi32 感谢您的回复。但是如果没有异步映射中的等待,就不可能再等待函数的重新结果?
    【解决方案4】:

    我建议使用上面提到的 Promise.all,但如果你真的想避免这种方法,你可以做一个 for 或任何其他循环:

    const arr = [1,2,3,4,5];
    let resultingArr = [];
    for (let i in arr){
      await callAsynchronousOperation(i);
      resultingArr.push(i + 1)
    }
    

    仅供参考:如果您想迭代数组的项目,而不是索引(@ralfoide 的评论),请在 let i in arr 语句中使用 of 而不是 in

    【讨论】:

    • Promise.all 对于数组的每个元素都是异步的。这将是一个同步,它必须等待完成一个元素才能开始下一个元素。
    • 对于那些尝试这种方法的人,请注意 for..of 是迭代数组内容的正确方法,而 for..in 迭代索引。
    【解决方案5】:

    使用modern-async's map()的解决方案:

    import { map } from 'modern-async'
    
    ...
    const result = await map(myArray, async (v) => {
        ...
    })
    

    使用该库的优点是您可以使用mapLimit()mapSeries() 控制并发。

    【讨论】:

      【解决方案6】:

      你可以使用:

      for await (let resolvedPromise of arrayOfPromises) {
        console.log(resolvedPromise)
      }
      

      https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of

      如果您想改用Promise.all(),您可以使用Promise.allSettled() 所以你可以更好地控制被拒绝的承诺。

      https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled

      【讨论】:

        【解决方案7】:

        我在 BE 方面有一项任务,即从 repo 中查找所有实体,并添加一个新的属性 url 并返回到控制器层。这就是我实现它的方式(感谢 Ajedi32 的回复):

        async findAll(): Promise<ImageResponse[]> {
            const images = await this.imageRepository.find(); // This is an array of type Image (DB entity)
            const host = this.request.get('host');
            const mappedImages = await Promise.all(images.map(image => ({...image, url: `http://${host}/images/${image.id}`}))); // This is an array of type Object
            return plainToClass(ImageResponse, mappedImages); // Result is an array of type ImageResponse
          }
        

        注意:图片(实体)没有属性 url,但 ImageResponse - 有

        【讨论】:

          【解决方案8】:

          如果您使用的不是原生 Promises 而是 Bluebird,还有另一种解决方案。

          你也可以尝试使用Promise.map(),混合array.map和Promise.all

          在你的情况下:

            var arr = [1,2,3,4,5];
          
            var results: number[] = await Promise.map(arr, async (item): Promise<number> => {
              await callAsynchronousOperation(item);
              return item + 1;
            });
          

          【讨论】:

          • 不同的是 - 它不会并行运行所有操作,而是按顺序执行它们。
          • @AndreyTserkus Promise.mapSeriesPromise.each 是连续的,Promise.map 一次启动它们。
          • @AndreyTserkus 您可以通过提供concurrency 选项并行运行所有或部分操作。
          • 值得一提的是,它不是普通的 JS。
          【解决方案9】:

          如果你映射到一个 Promise 数组,你可以将它们全部解析为一个数字数组。见Promise.all

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多