Используйте асинхронное ожидание с массивом.карта

#javascript #node.js #express #mongoose #async-await

Вопрос:

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

 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 У меня есть много операторов ожидания во внутренней функции. На самом деле это длинная функция, и я просто упростил ее, чтобы сделать ее читаемой. Я добавил вызов ожидания, чтобы было понятнее, почему он должен быть асинхронным.

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

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

Ответ №1:

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

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

Что вы, вероятно, хотите сделать, так это вызвать Promise.all массив, возвращаемый, map чтобы преобразовать его в одно обещание, прежде 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;
}));
 

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

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

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

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

2. @DanielPendergast Это для аннотаций типа в машинописном тексте.

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

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

5. @Ajedi32 thx за ответ. Но без ожидания в асинхронной карте больше невозможно ожидать повторного результата функции?

Ответ №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();
 

Также кодовый ключ.

Обратите внимание, что мы только «ждем» обещания.все. Мы вызываем calc без «ожидания» несколько раз и сразу же собираем множество нерешенных обещаний. Тогда Обещай.все ожидает разрешения всех из них и возвращает массив с разрешенными значениями по порядку.

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

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

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

Ответ №3:

Для этого есть другое решение, если вы используете не собственные обещания, а Bluebird.

Вы также можете попробовать использовать Promise.map(), смешав массив.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. @Andreytserk Promise.mapSeries или Promise.each являются последовательными, Promise.map запускает их все сразу.

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

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

Ответ №4:

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

Ответ №5:

Вы можете использовать:

 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

Ответ №6:

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

 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 оператора.

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

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

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

Ответ №7:

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

 import { map } from 'modern-async'

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

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

Ответ №8:

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