Created
March 12, 2019 18:59
-
-
Save BridgeAR/b34f606b34c341bf64e04aebb04e32bb to your computer and use it in GitHub Desktop.
A simple and strict promise map implementation.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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