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 await
ing 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);
});
Array.prototype.map
synchronously loops through an array and transforms each element to the return value of its callback.Both examples return a
Promise
.async
functions always return aPromise
.getResult
returns aPromise
.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 */);
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.
Either run the promises in parallel and collect the results with
Promise.all
or run them sequentially using a for * loop orArray.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
When Should I Use Arrow Functions in Ecmascript 6
How to Use Unicode-Aware Regular Expressions in JavaScript
How Does "This" Keyword Work Within a Function
JavaScript Get Clipboard Data on Paste Event (Cross Browser)
Asp.Net MVC Jsonresult Date Format
How to Use Formdata For Ajax File Upload
Facebook Js Sdk'S Fb.API('/Me') Method Doesn't Return the Fields I Expect in Graph API V2.4+
Uploading Both Data and Files in One Form Using Ajax
All Falsey Values in JavaScript
Formatting a Number With Exactly Two Decimals in JavaScript
How to Check If an Object Has a Specific Property in JavaScript
Creating a New Dom Element from an HTML String Using Built-In Dom Methods or Prototype
Merge/Flatten an Array of Arrays
How to Do Case Insensitive String Comparison
JavaScript Object Bracket Notation ({ Navigation } =) on Left Side of Assign