Skip to content

Commit 7b9a105

Browse files
committed
feat: add rejectLate option, switch to opts
BREAKING CHANGE: the function now expects an opts parameter
1 parent e466787 commit 7b9a105

3 files changed

Lines changed: 58 additions & 18 deletions

File tree

README.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,24 @@ const promiseCallLimit = require('promise-call-limit')
1010
const things = getLongListOfThingsToFrobulate()
1111

1212
// frobulate no more than 4 things in parallel
13-
promiseCallLimit(things.map(thing => () => frobulateThing(thing)), 4)
13+
promiseCallLimit(things.map(thing => () => frobulateThing(thing)), {
14+
limit: 4 })
1415
.then(results => console.log('frobulated 4 at a time', results))
1516
```
1617

1718
## API
1819

19-
### promiseCallLimit(queue Array<() => Promise>, limit = defaultLimit)
20-
21-
The default limit is the number of CPUs on the system - 1, or 1.
22-
23-
The reason for subtracting one is that presumably the main thread is taking
24-
up a CPU as well, so let's not be greedy.
20+
### promiseCallLimit(queue Array<() => Promise>, opts<Object>)
21+
22+
opts can contain:
23+
- limit: specified concurrency limit. Defaults to the number of
24+
CPUs on the system minus one. Presumably the main thread is taking
25+
up a CPU as well, so let's not be greedy. In the case where there
26+
is only one cpu the limit will default to 1.
27+
- rejectLate: if true, then any rejection will not prevent the rest of
28+
the queue from running. Any subsequent rejections will be ignored,
29+
and the first rejection will be what the function finally rejects
30+
with.
2531

2632
Note that the array should be a list of Promise-_returning_ functions, not
2733
Promises themselves. If you have a bunch of Promises already, you're best

index.js

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,24 @@ const os = require('os')
55

66
/* istanbul ignore next - version-specific workaround */
77
const defLimit = 'availableParallelism' in os
8-
? os.availableParallelism()
9-
: Math.max(1, os.cpus().length)
8+
? Math.max(1, os.availableParallelism() - 1)
9+
: Math.max(1, os.cpus().length - 1)
1010

11-
const callLimit = (queue, limit = defLimit) => new Promise((res, rej) => {
11+
const callLimit = (queue, { limit = defLimit, rejectLate } = {}) => new Promise((res, rej) => {
1212
let active = 0
1313
let current = 0
1414
const results = []
1515

16+
// Whether or not we rejected, distinct from the rejection just in case the rejection itself is falsey
1617
let rejected = false
18+
let rejection
1719
const reject = er => {
1820
if (rejected)
1921
return
2022
rejected = true
21-
rej(er)
23+
rejection = er
24+
if (!rejectLate)
25+
rej(rejection)
2226
}
2327

2428
let resolved = false
@@ -31,22 +35,27 @@ const callLimit = (queue, limit = defLimit) => new Promise((res, rej) => {
3135

3236
const run = () => {
3337
const c = current++
34-
if (c >= queue.length) {
35-
return resolve()
36-
}
38+
if (c >= queue.length)
39+
return rejected ? reject() : resolve()
3740

3841
active ++
3942
results[c] = queue[c]().then(result => {
4043
active --
4144
results[c] = result
45+
return result
46+
}, (er) => {
47+
active --
48+
reject(er)
49+
}).then(result => {
50+
if (rejected && active === 0)
51+
return rej(rejection)
4252
run()
4353
return result
44-
}, reject)
54+
})
4555
}
4656

47-
for (let i = 0; i < limit; i++) {
57+
for (let i = 0; i < limit; i++)
4858
run()
49-
}
5059
})
5160

5261
module.exports = callLimit

test/basic.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ t.test('two by two', t => {
2727
calledTre = true
2828
res(3)
2929
}, 50)),
30-
], 2).then(res => t.strictSame(res, [1, 2, 3]))
30+
], { limit: 2 }).then(res => t.strictSame(res, [1, 2, 3]))
3131
})
3232

3333
t.test('rejection', t => t.rejects(callLimit([
@@ -39,3 +39,28 @@ t.test('triple rejection', t => t.rejects(callLimit([
3939
() => Promise.reject(new Error('poop')),
4040
() => Promise.reject(new Error('poop')),
4141
]), { message: 'poop' }))
42+
43+
t.test('late rejection', async t => {
44+
const results = []
45+
await t.rejects(callLimit([
46+
() => new Promise(resolve => setTimeout(() => {
47+
results.push('first success')
48+
resolve('ok')
49+
}, 50)),
50+
() => new Promise((_, reject) => {
51+
setTimeout(() => {
52+
results.push('slow rejection')
53+
reject(new Error('slow rejection'))
54+
}, 100)
55+
}),
56+
() => {
57+
results.push('fast rejection')
58+
return Promise.reject(new Error('fast rejection'))
59+
},
60+
() => new Promise(resolve => setTimeout(() => {
61+
results.push('second success')
62+
resolve('ok 2')
63+
}, 50)),
64+
], { limit: 2, rejectLate: true }), { message: 'fast rejection' })
65+
t.match(results, ['first success', 'fast rejection', 'slow rejection', 'second success'])
66+
})

0 commit comments

Comments
 (0)