Use Async Await With Array.Map

Use async await with Array.map

The problem here is that you are trying to await an array of promises rather than a Promise. This doesn't do what you expect.

When the object passed to await is not a Promise, await simply returns the value as-is immediately instead of trying to resolve it. So since you passed await an array (of Promise objects) here instead of a Promise, the value returned by await is simply that array, which is of type Promise<number>[].

What you probably want to do is call Promise.all on the array returned by map in order to convert it to a single Promise before awaiting it.

According to the MDN docs for Promise.all:

The Promise.all(iterable) method returns a promise that resolves
when all of the promises in the iterable argument have resolved, or
rejects with the reason of the first passed promise that rejects.

So in your case:

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

This will resolve the specific error you are encountering here.

Depending on exactly what it is you're trying to do you may also consider using Promise.allSettled, Promise.any, or Promise.race instead of Promise.all, though in most situations (almost certainly including this one) Promise.all will be the one you want.

Async/Await inside Array#map()

You can't do this as you imagine, because you can't use await if it is not directly inside an async function.

The sensible thing to do here would be to make the function passed to map asynchronous. This means that map would return an array of promises. We can then use Promise.all to get the result when all the promises return. As Promise.all itself returns a promise, the outer function does not need to be async.

const someFunction = (myArray) => {
const promises = myArray.map(async (myValue) => {
return {
id: "my_id",
myValue: await service.getByValue(myValue)
}
});
return Promise.all(promises);
}

Async Await map not awaiting async function to complete inside map function before mapping next item

.map() is not async or promise aware. It just dutifully takes the value you return from its callback and stuffs it in the result array. Even though it's a promise in your case, it still just keeps on going, not waiting for that promise. And, there is nothing you can do in that regard to change the .map() behavior. That's just the way it works.

Instead, use a for loop and then await your async function inside the loop and that will suspend the loop.


Your structure:

await Promise.all(array.map(item => anAsyncFunction(item)));

is running all the anAsyncFunction() calls in parallel and then waiting for all of them to finish.


To run them sequentially, use a for loop and await the individual function call:

const fn = async() => {
const array = [0, 1, 2];
console.log('begin');
for (let item of array) {
await anAsyncFunction(item);
}
console.log('finished');
return;
}

This is an important thing to know that none of the array iteration methods are async aware. That includes .map(), .filter(), .forEach(), etc... So, if you want to await something inside the loop in order to sequence your async operations, then use a regular for loop which is async aware and will pause the loop.

Can't use async/await with Axios and array.map() with settimeout inside it

I managed to make it by using for await, and setTimeout, as I was needind to make only 3 api calls per second, it worked.

follow the code:

import { setTimeout } from 'timers/promises';
import axios from 'axios';

const result = [];
let counter = 0;

for await (let item of data) {
counter+=1

const numeroPedidoBling = item?.numeroPedidoBling;

if(counter%3 === 0) {
await setTimeout(1000)
}


const orderData = await axios.get(`${BLING_URL}/${numeroPedidoBling}/json?apikey=${APIKEY}`, { headers: {
'Accept': "application/json"
}})

result.push(orderData?.data?.retorno);
}

map() function with async/await

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>

How to use async and await inside a map function for sequential executing

You have to await promises of the map function to get results in order.

async function main() {
const items = []; // Fill your items
const articles = []; // Fill your articles

// Async map function return promise for each item
const promises = items.map(async (item, i) => {
console.log(item.FieldValuesAsText.FileRef);
const itemx = await Promise.all([
w.getFileByServerRelativeUrl(item.FieldValuesAsText.FileRef).getItem()
]);

console.log(item);
var likes;
likes = await Promise.all(itemx.getLikedByInformation());
console.log("like " + likes.likeCount);
const comments = await Promise.all(itemx.comments.get());
console.log("Comments Count " + comments.length);

// Create new object by appending articles[i],likes,comments
return {
...articles[i],
likecount: likes.likeCount,
commentcount: comments.length,
FileRef: item.FieldValuesAsText.FileRef
};
});

// Here you have everything in order.
const newst = await Promise.all(promises);
}

Each map item function runs asynchronously so there is no guarantee of order inside the map function, but you can return value from the map function which can be resolved to an ordered array by using await Promise.all() since the promise array returned by the map function is in the correct order.

Why does using async-await in map function still return promises and not the resolved values?

Because Array.map method itself is not an async method. In fact, I must remember you that JavaScript is a single threaded language. The only operations that can really happen simultaneously are I/O operations handled by your platform.

With that being said, in your case, Array.map will loop through your whole array and will return a new array containing the returned values from your callback for each original item.

An Async function always immediately and implicitly returns a promise that will then be resolved to the explicitly returned value of your function.

That is why you get an array of promises.

What you can do then, if you want to use the responses, is something like this:

const allImages = list.map(async (imageLink) => {
const image = await axios.get(imageLink);
return image.data.primaryImage;
});

Promise.all(allImages).then((responses) => {
console.log(responses); // THIS ARE THE RESPONSES YOU ARE LOOKING FOR.
});

Please let me know if you have any other doubt.

In response to comments:

JS code execution can never be paused, it would freeze your engine and would break the entire purpose of JS async implementation.

When you are inside an async function and find an await statement, code execution 'jumps' outside of the async scope 'promising' to resume where it left as soon as the Promise related to the await statement is fulfilled.

JS async/await is just syntactic sugar to deal with promises. And promises are just syntactic sugar to deal with callbacks.

See the following example using your code:

let count = 0;

const allImages = list.map(async (imageLink) => {
const image = await axios.get(imageLink);

// CODE EXECUTION 'STOPS' HERE, BUT CONTINUES OUTSIDE OF THE ASYNC SCOPE. Array.map PASSES TO NEXT ITEM.
count++;
console.log('A' + count);

return image.data.primaryImage; // THIS RESOLVES THE PROMISE.
});

// CODE EXECUTION CONTINUES
console.log('B');

Promise.all(allImages).then(() => {
// ALL PROMISES HAVE BEEN FULFILLED.
console.log('C');
});

/*

EXPECTED OUTPUT ORDER:

B
A1, A2, A3... (depending on http responses arrival order)
C

*/

Hopefully a more intuitive example that should work as you expect:

(async () => {
for (let i = 0; i < list.length; i++) {
const imageLink = list[i];
const image = await axios.get(imageLink);
// CODE EXECUTION 'STOPS' HERE.
list[i] image.data.primaryImage;
}

console.log(list); // AN ARRAY OF RESPONSES.
})();

// HOWEVER, CODE EXECUTION NEVER REALLY STOPS AND CONTINUES HERE. OUTSIDE OF THE async SCOPE.

Using Async/Await with Array.map

My question is that I have other promises inside and was wondering if the Promise.all() will also execute those inside the map in the correct order.

No.

Promise.all() will create a promise which resolves when all the promises passed to it resolve. It has no influence over what order those promises resolve in (and couldn't because they will have started running before Promise.all is invoked).

If you want to deal with each value of images in sequence (rather than in parallel) then use a regular for loop.



Related Topics



Leave a reply



Submit