Best way to call an asynchronous function within map?
One approach is Promise.all
(ES6).
This answer will work in Node 4.0+. Older versions will need a Promise polyfill or library. I have also used ES6 arrow functions, which you could replace with regular function
s for Node < 4.
This technique manually wraps request.get
with a Promise. You could also use a library like request-promise.
function urlToBase64(url) {
return new Promise((resolve, reject) => {
request.get(url, function (error, response, body) {
if (!error && response.statusCode == 200) {
resolve("data:" + response.headers["content-type"] + ";base64," + new Buffer(body).toString('base64'));
} else {
reject(response);
}
});
})
}
// Map input data to an Array of Promises
let promises = input.map(element => {
return urlToBase64(element.image)
.then(base64 => {
element.base64Data = base64;
return element;
})
});
// Wait for all Promises to complete
Promise.all(promises)
.then(results => {
// Handle results
})
.catch(e => {
console.error(e);
})
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.
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.
How can I call an ASYNC function inside of .map for populating a table in REACT?
async
functions always return promises. You can't fetch data asynchronously during rendering.
You need a useEffect
hook to run the function which gathers the data, and then a useState
hook to store it, along with logic to show a loading state while you wait for it.
const MyRow = ({ info }) => {
const [currentPrice, setCurrentPrice] = useState(null);
useEffect(() => {
fetchCurrentPrice.getPrice(info.name).then(setCurrentPrice);
}, [info]);
return <tr>
<td key={info.name}>{info.name}</td>
<td key={info.price}>{info.price}</td>
<td key={info.amount}>{info.amount}</td>
<td>{currentPrice ?? <Loading />}</td>
</tr>
}
with
{tableInfo.map(info => <MyRow key={info.name} info={info} />)}
async/await inside arrow functions (Array#map/filter)
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.
How to await asynchronous map function?
You basically have two problems:
- You are ignoring the return value of the
map
- The
map
will return an array of Promises and you aren'tawait
ing them all
So:
const promises = allIds.map(...);
const replacement_ids = await Promise.all(promises);
return replacement_ids;
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>
Related Topics
How to Get the Selected Text in a Textarea
Tolocaledatestring() Changes in Ie11
How to Get the Destination Url for the Onbeforeunload Event
Dangerous Implications of Allman Style in JavaScript
Merging or Combining Two Onedit Trigger Functions
Differencebetween Bower and Npm
Are Loops Really Faster in Reverse
How to Find First Element of Array Matching a Boolean Condition in JavaScript
Selected Text Event Trigger in JavaScript
Check for Special Characters in String
Using Variable Keys to Access Values in JavaScript Objects
How Does Asynchronous JavaScript Execution Happen? and When Not to Use Return Statement
Difference Between Using Bracket ('[]') and Dot ('.') Notation
Need to Escape a Special Character in a Jquery Selector String