Используйте 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работать вместе?


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

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


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


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

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 ожидает разрешения всех из них и возвращает массив с разрешенными значениями по порядку.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.