Best Way to Call an Asynchronous Function Within Map

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 functions 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 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.

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:

  1. You are ignoring the return value of the map
  2. The map will return an array of Promises and you aren't awaiting 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);
});
  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>


Related Topics



Leave a reply



Submit