TL:DR - Asynchronous, high order array-functions return an array of promises. In order to resolve each of these, you can use one of the following methods:
-
Promise.all([ /* ... array of promises ... */ ])
Wait for all promises to be resolved, throws errors -
Promise.allSettled([/* ... array or promises ...*/ ])
Wait for all promises to be resolved or rejected, requires manual error handling
A variable assignation using .map()
then looks something like this:
const promiseArray = iterableArray.map(async (element) => {
// ... async code
return result;
});
const dataArray = Promise.all(promiseArray);
A simple use case
While high order functions have lots of perks, I recently noticed they were not natively capable of handling promises' syntactic sugar very well.
I noticed this problem when developing on serverside Node.js code, which was meant to accept an array of files from an incoming client as formdata and save it to a database. Instead of returning the response I'd expect, namely an array with values, the below function returned me an array of Promises
:
- First, the npm Formidable library would handle form parsing and give me a
files
- object. It would be available only inside the callback's scope. - Inside
files
, the first property would indicate the file - array:const filePropertyName = Object.keys(files)[0]
- Having identified these two, I could now iterate through the array of files.
- For each file, I would then prepare a payload and call an SQL - stored procedure to asynchronously write this file into the database, using mssql.
- Each successfully performed stored procedure would return me a fileId that uniquely identifies each uploaded file. I would store it in
fileIds
(see code below) and then send the array back to the client.
So far so good, right? Everything that comes after cannot be much harder. Here's the code:
// Iterate through the array of files identified by its form property
// ('name' of the client's form field)
const fileIds = files[filePropertyName].map(async (file /* object */) => {
// Use a private function to create a payload for stored procedure
// (In order to work, it required some intel from other formfields)
const payload = this._handleSetUploadPayload(fields,file);
// Create a new FileModel
const File = new FileModel(storedProcedureName);
// Use its uploadFile method to trigger the stored procedure
return await File.uploadFile(payload);
});
Well, not so fast. After sending three files down the API, what fileIds
contained was not exactly what I've been looking for. When I started to debug, I saw the following result:
[Promise {<pending>}, Promise {<pending>}, Promise{<pending>}]
I was puzzled for a moment. And frustrated. So I started searching MDN and found an explanation (step 9: return A
).
The solution
In my own words, that'll be:
The
.map()
algorithm applies an async callback to each element of an array, creating promises as it does. However, the returned result by.map()
is no promise, but an array of promises.
That was an answer I could live with. So I changed the code accordingly, primarily by adding Promise.all()
and - voila, it started to work:
const fileIdsPromises = files[filePropertyName].map(async (file) => {
const payload = this._handleSetUploadPayload(fields,file);
const File = new FileModel(storedProcedureName);
const fileId = await File.uploadFile(payload);
return fileId
});
const fileIds = await Promise.all(fileIdsPromises);
This post was originally published at https://blog.q-bit.me/use-async-await-with-high-order-functions/
Thank you for reading. If you enjoyed this article, let's stay in touch on Twitter 🐤 @qbitme
Top comments (3)
I've been doing this kind of thing for a long time, but nowerdays I rather just use a for...of loop and regular async/await syntax :)
Good point. Frankly speaking, for ... of is what made it into the final codebase as it's easier to read & interpret
Was having this issue where I was trying to push to array inside the map and I realized it was not working because of aynch behaviour of JavaScript.
This helped a lot! Thank you!