функция map () с асинхронным ожиданием - PullRequest
1 голос
/ 18 марта 2019

Существует довольно много опубликованных тем о том, как асинхронное / ожидающее поведение ведет себя в функции карты javascript, но все же подробное объяснение в следующих двух примерах было бы неплохо:

  const resultsPromises = myArray.map(async number => {
    return await getResult(number);
  });
  const resultsPromises = myArray.map(number => {
    return getResult(number);
  });

отредактировано:это, если, конечно, вымышленный случай, поэтому просто открыт для дебатов, почему, как и когда функция map должна ждать ключевое слово await.Решения, как изменить этот пример, вызов Promise.all () не является целью этого вопроса.
getResult - асинхронная функция

Ответы [ 5 ]

2 голосов
/ 18 марта 2019

Если цель первого фрагмента кода состояла в том, чтобы иметь вызов .map, который ожидает разрешения всех Обещаний перед возвратом (и чтобы эти обратные вызовы выполнялись последовательно), я боюсь, что это не сработаеттот.Функция .map не знает, как это сделать с функциями async.

Это можно продемонстрировать с помощью следующего кода:

const array = [ 1, 2, 3, 4, 5 ];
      
function getResult(n)
{
    console.log('starting ' + n);

    return new Promise(resolve => {
        setTimeout(() => {
            console.log('finished ' + n);
            resolve(n);
        }, 1000 * (Math.random(5) + 1));
    });
}

let promises = array.map(async (n) => {
    return await getResult(n);
});

console.log('map finished');

Promise.all(promises).then(console.log);

Здесь вы увидите, что вызов .map завершается непосредственно перед завершением любой из асинхронных операций.

2 голосов
/ 18 марта 2019

Array.prototype.map() - это функция, которая преобразует массивы. Он отображает один массив в другой массив. Наиболее важной частью сигнатуры его функции является обратный вызов. Обратный вызов вызывается для каждого элемента в массиве, и этот обратный вызов возвращает то, что помещается в новый массив, возвращаемый map.

Он не делает ничего особенного с тем, что возвращается. Он не вызывает .then() на предметах, он не await ничего. Синхронно преобразует данные.

Это означает, что если обратный вызов возвращает Promise (что делают все функции async), все обещания будут "горячими" и выполняются параллельно.

В вашем примере, если getResult() возвращает Promise или сам по себе является асинхронным, между вашими реализациями на самом деле нет разницы. resultsPromises будет заполнено Promise s, которые могут быть или не быть решены.

Если вы хотите подождать, пока все закончится, прежде чем двигаться дальше, вам нужно использовать Promise.all().

Кроме того, если вы хотите, чтобы одновременно работал только 1 getResults(), используйте обычный цикл for и await внутри цикла.

1 голос
/ 19 марта 2019

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

const resultsPromises = myArray.map(async number => {
  return await getResult(number);
});
const resultsPromises = myArray.map(number => {
  return getResult(number);
});
  1. Array.prototype.map синхронно проходит через массив и преобразует каждый элемент в возвращаемое значение его обратного вызова.

  2. Оба примера возвращают Promise.

    • async функции всегда возвращают Promise.

    • getResult возвращает Promise.

    • Поэтому, если нет ошибок, вы можете рассматривать их в псевдокоде как:

const resultsPromises = myArray.map(/* map each element to a Promise */);

Как указано zero298 и alnitak продемонстрировал , это очень быстро (синхронно) запускает каждое обещание по порядку;однако, поскольку они выполняются параллельно, каждое обещание будет разрешено / отклонено по своему усмотрению и, скорее всего, не будет выполнено (выполнено или отклонено) по порядку.

Либо выполните обещания параллельнои собрать результаты с помощью Promise.all или запустить их последовательно, используя for * loop или Array.prototype.reduce.

В качестве альтернативы, вы можетеиспользуйте сторонний модуль для цепных асинхронных методов JavaScript Я поддерживаю, чтобы очистить вещи и - возможно - сделать так, чтобы код соответствовал вашей интуиции того, как может работать операция асинхронная карта :

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

const getResult = async n => {
  await delay(Math.random() * 1000);
  console.log(n);
  return n;
};

(async () => {
  console.log('parallel:');
  await AsyncAF([1, 2, 3]).map(getResult).then(console.log);
  
  console.log('sequential:');
  await AsyncAF([1, 2, 3]).series.map(getResult).then(console.log)
})();
<script src="https://unpkg.com/async-af@7.0.12/index.js"></script>
1 голос
/ 18 марта 2019

Если getResult всегда возвращает обещание и никогда не выдает ошибку, тогда оба будут вести себя одинаково.

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

Как было сказано во многих комментариях, вам никогда не понадобится return await - это эквивалентно добавлению .then(result=>result) в конце цепочки обещаний - это (в основном) безвредно, но не нужно. Просто используйте return.

1 голос
/ 18 марта 2019

async/await полезно, если вы хотите сгладить код, удалив обратные вызовы .then() или если вы хотите неявно вернуть Обещание:

const delay = n => new Promise(res => setTimeout(res, n));

async function test1() {
  await delay(200);
  // do something usefull here
  console.log('hello 1');
}

async function test2() {
  return 'hello 2'; // this returned value will be wrapped in a Promise
}

test1();
test2().then(console.log);

Однако в вашем случае вы не используете await для замены .then() и не используете его для возврата неявного Promise, поскольку ваша функция уже возвращает Promise. Так что они не нужны.

Параллельное выполнение всех обещаний

Если вы хотите запустить все Обещания параллельно, я бы предложил просто вернуть результат getResult с map() и сгенерировать массив Обещаний. Обещания будут выполняться последовательно, но в конечном итоге будут выполняться параллельно.

const resultsPromises = indicators.map(getResult);

Тогда вы можете дождаться всех обещаний и получить разрешенные результаты, используя Promise.all():

const data = [1, 2, 3];

const getResult = x => new Promise(res => {
  return setTimeout(() => {
    console.log(x);
    res(x);
  }, Math.random() * 1000)
});

Promise.all(data.map(getResult)).then(console.log);

Последовательное выполнение Обещаний

Однако, если вы хотите запустить каждое Обещание последовательно и дождаться разрешения предыдущего Обещания, прежде чем запускать следующее, тогда вы можете использовать lower () и async/await следующим образом:

const data = [1, 2, 3];

const getResult = x => new Promise(res => {
  return setTimeout(() => {
    console.log(x);
    res(x);
  }, Math.random() * 1000)
});

data.reduce(async (previous, x) => {
  const result = await previous;
  return [...result, await getResult(x)];
}, Promise.resolve([])).then(console.log);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...