Use async / await with Javascript's .map() and other high-order functions

tq-bit - May 31 '21 - - Dev Community

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);
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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>}]
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .