Skip to content

Instantly share code, notes, and snippets.

@BridgeAR
Created March 12, 2019 18:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BridgeAR/b34f606b34c341bf64e04aebb04e32bb to your computer and use it in GitHub Desktop.
Save BridgeAR/b34f606b34c341bf64e04aebb04e32bb to your computer and use it in GitHub Desktop.
A simple and strict promise map implementation.
'use strict'
const assert = require('assert')
const fs = require('fs')
const util = require('util')
function promiseMapOrdered(arr, mapper, concurrency) {
// Do not use an `async` function! Otherwise the promise constructor won't
// notice sync errors.
return new Promise((resolve, reject) => {
// Functions returning a promise should always return promises. This is
// somewhat inconvenient for input validation as it could result in noticing
// the mistake too late.
assert(Array.isArray(arr))
assert(typeof mapper === 'function')
assert(typeof concurrency === 'number')
assert(concurrency >= 1)
// The entries of arr should not be of type promise, otherwise the
// concurrency won't have any effect as the async operation is already
// triggered. All that can still be done is calling the mapper function as
// soon as the promise is settled. But that can also easily be done with:
//
// const entries = await Promise.all([...]) const mapped = await
// Promise.all(entries.map(fn))
//
// This might be relaxed to e.g., accepting a number of promises <
// concurrency. But that also requires to resolve all entries that are a
// promise before passing them to the mapper function.
assert(arr.every((e) => !util.types.isPromise(e)))
let results = new Array(arr.length)
let current = 0
let done = 0
let rejected = false
const next = () => {
if (rejected)
return
const i = current++
// Note: mapper has to return a promise! Otherwise this will fail. That
// could be "fixed" by resolving arr[i] but that adds an extra tick per
// entry and it hides the fact that the mapper function is not doing what
// it should.
mapper(arr[i]).then((res) => {
results[i] = res
done++
if (done === arr.length) {
resolve(results)
} else if (current < arr.length) {
next()
}
}).catch((e) => {
results = null
rejected = true
// Instead of returning the very first error, it would also be possible
// to return an aggregate error. That would help to debug some
// applications.
reject(e);
})
}
for (let i = 0; i < Math.min(concurrency, arr.length); i++) {
next()
}
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment