TL: DR - Asynchronous, high order array functions return an array of promises. 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 errorsPromise.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 form data 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 field 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 was contained in fileIds
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);