简体   繁体   中英

map() function with async/await

There is quite some topics posted about how async/await behaves in javascript map function, but still, detail explanation in bellow two examples would be nice:

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

edited: this if of course a fictional case, so just opened for debate, why,how and when should map function wait for await keyword. solutions how to modify this example, calling Promise.all() is kind of not the aim of this question.
getResult is an async function

The other answers have pretty well covered the details of how your examples behave, but I wanted to try to state it more succinctly.

const resultsPromises = myArray.map(async number => {
  return await getResult(number);
});
const resultsPromises = myArray.map(number => {
  return getResult(number);
});
  1. Array.prototype.map synchronously loops through an array and transforms each element to the return value of its callback.

  2. Both examples return a Promise .

    • async functions always return a Promise .

    • getResult returns a Promise .

    • Therefore, if there are no errors you can think of them both in pseudocode as:

const resultsPromises = myArray.map(/* map each element to a Promise */);
  1. As zero298 stated and alnitak demonstrated , this very quickly (synchronously) starts off each promise in order; however, since they're run in parallel each promise will resolve/reject as they see fit and will likely not settle (fulfill or reject) in order.

  2. Either run the promises in parallel and collect the results with Promise.all or run them sequentially using a for * loop or Array.prototype.reduce .

Alternatively, you could use a third-party module for chainable asynchronous JavaScript methods I maintain to clean things up and--perhaps--make the code match your intuition of how an async map operation might work:

 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>

async/await is usefull when you want to flatten your code by removing the .then() callbacks or if you want to implicitly return a Promise:

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

However, in your case, you are not using await to replace a .then() , nor are you using it to return an implicit Promise since your function already returns a Promise. So they are not necessary.

Parallel execution of all the Promises

If you want to run all Promises in parallel, I would suggest to simply return the result of getResult with map() and generate an array of Promises. The Promises will be started sequentially but will eventually run in parallel.

const resultsPromises = indicators.map(getResult);

Then you can await all promises and get the resolved results using 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);

Sequential execution of the Promises

However, if you want to run each Promise sequentially and wait for the previous Promise to resolve before running the next one, then you can use reduce() and async/await like this:

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

Array.prototype.map() is a function that transforms Arrays. It map s one Array to another Array. The most important part of its function signature is the callback. The callback is called on each item in the Array and what that callback returns is what is put into the new Array returned by map .

It does not do anything special with what gets returned. It does not call .then() on the items, it does not await anything. It synchronously transforms data.

That means that if the callback returns a Promise (which all async functions do), all the promises will be "hot" and running in parallel.

In your example, if getResult() returns a Promise or is itself async, there isn't really a difference between your implementations. resultsPromises will be populated by Promise s that may or may not be resolved yet.

If you want to wait for everything to finish before moving on, you need to use Promise.all() .

Additionally, if you only want 1 getResults() to be running at a time, use a regular for loop and await within the loop.

If the intent of the first code snippet was to have a .map call that waits for all of the Promises to be resolved before returning (and to have those callbacks run sequentially) I'm afraid it doesn't work like that. The .map function doesn't know how to do that with async functions.

This can be demonstrated with the following code:

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

Where you'll see that the .map call finishes immediately before any of the asynchronous operations are completed.

If getResult always returns a promise and never throws an error then both will behave the same.

Some promise returning functions can throw errors before the promise is returned, in this case wrapping the call to getResult in an async function will turn that thrown error into a rejected promise, which can be useful.

As has been stated in many comments, you never need return await - it is equivalent to adding .then(result=>result) on the end of a promise chain - it is (mostly) harmless but unessesary. Just use return .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM