Explain Codes LogoExplain Codes Logo

Use async await with Array.map

javascript
async-await
promise-combinators
error-handling
Nikita BarsukovbyNikita Barsukov·Nov 22, 2024
TLDR
// Always-on-time function to handle multiple async requests async function asyncMap(array, asyncFn) { return Promise.all(array.map(item => asyncFn(item))); } // Example (Quick maths) 🧮: const results = await asyncMap([1, 2, 3], async num => { return num * 2; // Our async operation just doubles the numbers }); console.log(results); // Logs [2, 4, 6] to the console

By using Array.map, you dispatch the async operations concurrently. You then await completion of all calls with Promise.all. The asyncMap function is a sweet bit of sugar. Our example just multiplies numbers for demonstration purposes.

Understanding Promise Combinators: The Fantastic Four

When juggling with multiple promises (not recommended IRL), familiarise yourself with following Promise combinators:

  1. Promise.all: Waits until all its promises are kept—no exceptions.
  2. Promise.allSettled: Puts all its promise-debts on a payment plan, no matter how those plans end.
  3. Promise.any: Celebrates the successful resolution of a single promise. Quite optimistic!
  4. Promise.race: Here, every promise for itself. First to resolve (or reject) wins the race.

In most scenarios, Promise.all will be your go-to. But Promise.allSettled can be your best pal if you need to know the fate of all operations, success or fail.

Error handling: Anticipating Promise broken (sad face)

When performing asynchronous mapping, you must contain any chaos. This is where error handling comes in:

async function safeAsyncMap(array, asyncFn) { try { return await Promise.all(array.map(item => asyncFn(item))); } catch (error) { console.error("A wild error appeared:", error); // Gotta catch 'em all (errors) } }

Wrapping the await Promise.all within a try/catch block lets you catch errors and handle them with grace and dignity, ensuring your app doesn't go caput.

Enhancing Readability: Bullet Speed.

Arrow functions within Array.map can make your async mappings look as clean as your room (presumably):

const asyncOperation = async item => { /* Secret async sauce, mmm. */ }; const results = await Promise.all(items.map(asyncOperation));

Pairing arrow functions & async/await can significantly reduce boilerplate and turbocharge the clarity of your code.

TypeScript: The Butcher's scale of Javascript

With TypeScript, you're assured of type safety while using Array.map. This increases the likelihood that your code behaves as expected, avoiding sudden heating of your machine.

Testing Corners in Async Iterations

For testing async/await mappings, simulate async operations. The randomDelay function is great for network latency simulation or file I/O tests:

function randomDelay(val) { return new Promise(resolve => setTimeout(() => { console.log(`Waited long? Enjoy your ${val}.`); resolve(val); }, Math.random() * 1000)); } let testResults = await Promise.all(arr.map(async n => await randomDelay(n)));

Testing with induced delays helps ensure your code's behavior under asynchronous conditions.

Bluebird Library: Promise's Cool Cousin

Bluebird offers Promise.map() that provides concurrency control—perfect for edge cases:

let Bluebird = require('bluebird'); // No actual birds were harmed 🐦 let results = await Bluebird.map(array, asyncFn, { concurrency: 2 }); // Spawn only 2 async calls at a time

This pattern helps manage system resources when bursting with concurrent operations.

Performance Juicing

When processing large datasets or heavy operations, optimisation matters:

  1. Cap concurrency: Keep a leash on simultaneous operations to prevent overcrowding of the event loop.
  2. Chunk processing: Divide the data set into smaller chunks and process them in isolation.
  3. Bypass unnecessary computations: Skip iterations if they won't affect the result.

Keeping Result Order: First In, First Out

When using Array.map and await on an array of promises, the resolution order is the same as the triggering order:

const ids = [1, 2, 3]; // The order of the fellowship const fetchDataForId = async id => { /* Fetch data using the Ring's power */ }; const dataInOrder = await Promise.all(ids.map(fetchDataForId));

Regardless of promise resolution times, dataInOrder maintains the order of ids, preventing any "unexpected" occurrences.