Использовать асинхронное ожидание с Array.map

#javascript #arrays #asynchronous

#javascript #typescript #обещание #асинхронное ожидание #ecmascript-2017

Вопрос:

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

 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 работать вместе?

Комментарии:

1. Почему вы пытаетесь превратить синхронную операцию в асинхронную операцию? arr.map() является синхронным и не возвращает обещание.

2. Вы не можете отправить асинхронную операцию функции, например map , которая ожидает синхронную, и ожидать, что она сработает.

3. @jfriend00 У меня есть много операторов await во внутренней функции. На самом деле это длинная функция, и я просто упростил ее, чтобы сделать ее читаемой. Теперь я добавил вызов await, чтобы было понятнее, почему он должен быть асинхронным.

4. Вам нужно ожидать чего-то, что возвращает обещание, а не что-то, что возвращает массив.

5. Полезно понимать, что каждый раз, когда вы помечаете функцию как async , вы заставляете эту функцию возвращать обещание. Итак, конечно, карта async возвращает массив обещаний 🙂

Ответ №1:

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

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

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

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

Promise.all(iterable) Метод возвращает обещание, которое разрешается, когда все обещания в аргументе 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;
}));
 

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

В зависимости от того, что именно вы пытаетесь сделать, вы также можете рассмотреть возможность использования Promise.allSettled , Promise.any , или Promise.race вместо Promise.all , хотя в большинстве ситуаций (почти наверняка включая эту) Promise.all будет тот, который вы хотите.

Комментарии:

1. Что означают : двоеточия?

2. @DanielPendergast Это для аннотаций типов в TypeScript.

3. В чем разница между вызовом callAsynchronousOperation(item); с помощью и без await внутри функции async map?

4. @nerdizzle Это звучит как хороший кандидат на другой вопрос. В принципе, await функция with будет ждать завершения (или сбоя) асинхронной операции перед продолжением, в противном случае она просто немедленно продолжится без ожидания.

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

Комментарии:

1. За годы использования Javascript у меня всегда были проблемы с отображением массивов без нарушения порядка из-за асинхронной природы js. Я использовал обещанное. все, но никогда не думал, что он разрешает их как есть. Этот ответ буквально изменил то, как я сейчас пишу карты и другие вещи. Благослови Бог.

2. Спасибо за это решение, я нашел его гораздо более красноречивым и читаемым, чем другие предоставленные.

Ответ №3:

Для этого есть другое решение, если вы используете не собственные обещания, а 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;
  });
 

Комментарии:

1. Он отличается — он не выполняет все операции параллельно, а выполняет их последовательно.

2. @AndreyTserkus Promise.mapSeries или Promise.each являются последовательными, Promise.map запускают их все сразу.

3. @AndreyTserkus вы можете запускать все или некоторые операции параллельно, предоставив concurrency опцию.

4. Стоит отметить, что это не ванильный JS.

Ответ №4:

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

Ответ №5:

Это самый простой способ сделать это.

 await Promise.all(
    arr.map(async (element) => {
        ....
    })
)
 

Ответ №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:

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

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

К вашему СВЕДЕНИЮ: если вы хотите перебирать элементы массива, а не индексы (комментарий @ralfoide), используйте of вместо in let i in arr инструкции inside .

Комментарии:

1. Обещание. все они будут асинхронными для каждого элемента массива. Это будет синхронизация, она должна подождать завершения одного элемента, чтобы начать следующий.

2. Для тех, кто пытается использовать этот подход, обратите внимание, что for..of — это правильный способ итерации содержимого массива, тогда как for..in выполняет итерацию по индексам.

Ответ №8:

Решение с использованием modern-async map():

 import { map } from 'modern-async'

...
const result = await map(myArray, async (v) => {
    ...
})
 

Преимущество использования этой библиотеки заключается в том, что вы можете управлять параллелизмом с помощью mapLimit() или mapSeries().

Ответ №9:

У меня была задача на стороне BE найти все объекты из репозитория, добавить новый 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 — имеет

Ответ №10:

Это может кому-то помочь.

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