Используйте async await с Array.map

171

Учитывая следующий код:

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

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

которая выдает следующую ошибку:

TS2322: Тип «Обещание <номер> []» нельзя назначить типу «номер []». Тип «Обещание <номер> нельзя назначить типу« номер ».

Как я могу это исправить? Как я могу сделать async awaitи Array.mapработать вместе?

Alon
источник
6
Почему вы пытаетесь превратить синхронную операцию в асинхронную? arr.map()является синхронным и не возвращает обещание.
jfriend00
2
Вы не можете отправить асинхронную операцию функции, например map, которая ожидает синхронную операцию и ожидает, что она будет работать.
Еретик Обезьяна
1
@ jfriend00 У меня много ожиданий во внутренней функции. На самом деле это длинная функция, и я просто упростил ее, чтобы сделать ее читабельной. Я добавил теперь ожидание вызова, чтобы прояснить, почему он должен быть асинхронным.
Алон
Вам нужно ждать чего-то, что возвращает обещание, а не того, что возвращает массив.
Jfriend00
2
Полезно понять, что каждый раз, когда вы помечаете функцию как async, вы заставляете эту функцию возвращать обещание. Поэтому, конечно, карта асинхронности возвращает массив обещаний :)
Энтони Мэннинг-Франклин,

Ответы:

382

Проблема здесь в том, что вы пытаетесь awaitполучить массив обещаний, а не обещаний. Это не делает то, что вы ожидаете.

Когда переданный объект awaitне является Обещанием, он awaitпросто возвращает значение как есть, а не пытается его разрешить. Таким образом, поскольку вы передали awaitздесь массив (объектов Promise) вместо Promise, значение, возвращаемое await, является просто тем массивом, который имеет тип Promise<number>[].

Здесь вам нужно вызвать Promise.allмассив, возвращаемый для mapтого, чтобы преобразовать его в один Promise перед awaitего использованием.

Согласно документам MDN дляPromise.all :

Promise.all(iterable)Метод возвращает обещание , которое решает , когда все обещания в итерации аргумента решил, или отбросы с причиной первого прошедшим обещания, отвергающими.

Итак, в вашем случае:

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

Это устранит конкретную ошибку, с которой вы столкнулись здесь.

Ajedi32
источник
1
Что :означают двоеточия?
Даниил говорит: восстанови Монику
12
@DanielPendergast Это для аннотаций типов в TypeScript.
Ajedi32
В чем разница между вызовом callAsynchronousOperation(item);с и без awaitвнутри функции асинхронной карты?
занудство
@nerdizzle Это звучит как хороший кандидат на другой вопрос. Однако, в принципе, awaitфункция будет ожидать завершения (или сбоя) асинхронной операции, прежде чем продолжить, в противном случае она будет просто немедленно продолжена без ожидания.
Ajedi32
@ Ajedi32 спасибо за ответ. Но без ожидания в асинхронной карте больше невозможно ожидать результата функции?
занудство
16

Есть и другое решение, если вы используете не собственные обещания, а 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;
  });
Габриэль Чунг
источник
2
Он отличается - он не запускает все операции параллельно, а выполняет их последовательно.
Андрей Церкус
5
@AndreyTserkus Promise.mapSeriesили Promise.eachявляются последовательными, Promise.mapзапускает их все сразу.
Kiechlus
1
@AndreyTserkus вы можете запустить все или некоторые операции параллельно, предоставив concurrencyопцию.
11
Стоит отметить, что это не ванильный JS.
Михал
@Michal да, это SyntaxError
CS QGB
7

Если вы сопоставите массив с Обещаниями, вы сможете преобразовать их в массив чисел. Смотрите Promise.all .

Дэн Болье
источник
2

Я бы рекомендовал использовать Promise.all, как упомянуто выше, но если вы действительно хотите избежать такого подхода, вы можете сделать цикл for или любой другой цикл:

const arr = [1,2,3,4,5];
let resultingArr = [];
for (let i in arr){
  await callAsynchronousOperation(i);
  resultingArr.push(i + 1)
}
Beckster
источник
6
Promise.all будет асинхронным для каждого элемента массива. Это будет синхронизация, нужно подождать, чтобы закончить один элемент, чтобы начать следующий.
Сантьяго Мендоса Рамирес
Для тех, кто пробует этот подход, обратите внимание, что for..of является правильным способом итерации содержимого массива, тогда как for..in выполняет итерации по индексам.
Ральфоид
2

Решение ниже, чтобы обработать все элементы массива асинхронно И сохранить порядок:

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 без «await» несколько раз и сразу собираем массив нерешенных обещаний. Затем Promise.all ожидает разрешения всех из них и возвращает массив с разрешенными значениями по порядку.

Miki
источник