To streamline your code, consider utilizing node's built-in util.promisify
instead of relying on async.map
and async.js:
const { promisify } = require('util')
const fs = require('fs')
const files = ['file1', 'file2', 'file3']
Promise.all(files.map(promisify(fs.stat)))
.then(results => /* results is now an array of stats for each file */)
.catch(err => /* err is the first error to occur */)
Promises have become the go-to method for handling concurrency in modern JavaScript environments. They can seamlessly replace node-style error-first callbacks, following the format of (err, res) => { ... }
, which is commonly seen with async.map
.
Promises offer a solution to the issues associated with "callback hell" and provide a cleaner approach to asynchronous programming. If you still prefer using node-style callbacks, the below example showcasing asyncMap
and an async function demonstrates the orchestration of callbacks:
// Code example demonstrating asyncMap functionality
// Define a function that doubles a number after a delay
const delayedDouble = (x, callback) =>
setTimeout
( () =>
callback
( null
, x * 2
)
, 1000
)
// Implement asyncMap to handle async operations and callbacks
const asyncMap = (arr, func, cb) =>
{ const loop = (res, i) =>
i >= arr.length
? cb(null, res)
: func
( arr[i]
, (err, x) =>
err
? cb(err, null)
: loop
( [ ...res, x ]
, i + 1
)
)
return loop
( []
, 0
)
}
// Demonstrate the usage of asyncMap with a delayedDouble function
asyncMap
( [ 1, 2, 3 ]
, delayedDouble
, (err, res) =>
err
? console.error('error', err)
: console.log('result', res)
)
console.log('please wait 3 seconds...')
// please wait 3 seconds...
// <3 seconds later>
// result [ 2, 4, 6 ]
In the event of a potential error, asyncMap
handles error propagation effectively:
// Example showcasing error handling in asyncMap
const tenDividedBy = (x, callback) =>
setTimeout
( () =>
x === 0
? callback(Error('cannot divide 10 by zero'), null)
: callback(null, 10 / x)
, 1000
)
asyncMap
( [ 1, 0, 6 ]
, tenDividedBy
, (err, res) =>
err
? console.error('error', err)
: console.log('result', res)
)
// error Error: cannot divide 10 by zero
When executed without errors, asyncMap
returns the results as expected:
asyncMap
( [ 1, 2, 3, ]
, tenDividedBy
, (err, res) =>
err
? console.error('error', err)
: console.log('result', res)
)
// result [ 10, 5, 3.3333333333333335 ]
Transitioning to Promises offers a more streamlined approach, reducing callback nesting and providing automatic error handling:
// Refactored example using Promises for smoother asynchronous operations
// Implement asyncMap using Promises
const asyncMap = (arr, func) =>
{ const loop = (res, i) =>
i >= arr.length
? Promise.resolve(res)
: func(arr[i]).then(x => loop([...res, x], i + 1))
return loop ([], 0)
}
// Function using Promises to handle division
const tenDividedBy = x =>
x === 0
? Promise.reject(Error('cannot divide 10 by zero'))
: Promise.resolve(10 / x)
// Demonstrate the usage of asyncMap with Promises
asyncMap([1, 2, 0], tenDividedBy)
.then(res => console.log('result', res))
.catch(err => console.error('error', err))
// Error: cannot divide 10 by zero
asyncMap([1, 2, 3], tenDividedBy)
.then(res => console.log('result', res))
.catch(err => console.error('error', err))
// result [ 10, 5, 3.3333 ]
While manual implementation is great for learning purposes, leveraging Promise.all
eliminates the need for custom solutions like asyncMap
and offers parallel processing for enhanced efficiency.