Skip to content

Commit 73f94dd

Browse files
committed
Rewrote "every"
1 parent f879754 commit 73f94dd

File tree

8 files changed

+219
-328
lines changed

8 files changed

+219
-328
lines changed

modern-async.d.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -63,18 +63,9 @@ declare module "findIndexLimit" {
6363
function findIndexLimit<V>(iterable: Iterable<V> | AsyncIterable<V>, iteratee: (value: V, index: number, iterable: Iterable<V> | AsyncIterable<V>) => Promise<boolean> | boolean, queueOrConcurrency: Queue | number, ordered?: boolean): Promise<number>;
6464
import Queue from "Queue";
6565
}
66-
declare module "everyLimit" {
67-
export default everyLimit;
68-
function everyLimit<V>(iterable: Iterable<V> | AsyncIterable<V>, iteratee: (value: V, index: number, iterable: Iterable<V> | AsyncIterable<V>) => Promise<boolean> | boolean, queueOrConcurrency: Queue | number): Promise<boolean>;
69-
import Queue from "Queue";
70-
}
7166
declare module "every" {
7267
export default every;
73-
function every<V>(iterable: Iterable<V> | AsyncIterable<V>, iteratee: (value: V, index: number, iterable: Iterable<V> | AsyncIterable<V>) => Promise<boolean> | boolean): Promise<boolean>;
74-
}
75-
declare module "everySeries" {
76-
export default everySeries;
77-
function everySeries<V>(iterable: Iterable<V> | AsyncIterable<V>, iteratee: (value: V, index: number, iterable: Iterable<V> | AsyncIterable<V>) => Promise<boolean> | boolean): Promise<boolean>;
68+
function every<V>(iterable: Iterable<V> | AsyncIterable<V>, iteratee: (value: V, index: number, iterable: Iterable<V> | AsyncIterable<V>) => Promise<boolean> | boolean, queueOrConcurrency: Queue | number): Promise<boolean>;
7869
}
7970
declare module "toArray" {
8071
export default toArray;
@@ -234,8 +225,6 @@ declare module "modern-async" {
234225
export { default as delayCancellable } from "delayCancellable";
235226
export { default as Delayer } from "Delayer";
236227
export { default as every } from "every";
237-
export { default as everyLimit } from "everyLimit";
238-
export { default as everySeries } from "everySeries";
239228
export { default as filter } from "filter";
240229
export { default as filterGenerator } from "filterGenerator";
241230
export { default as filterLimit } from "filterLimit";

src/every.mjs

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,30 @@
11

2-
import everyLimit from './everyLimit.mjs'
2+
import findIndexLimit from './findIndexLimit.mjs'
3+
import Queue from './Queue.mjs'
4+
import asyncWrap from './asyncWrap.mjs'
5+
import assert from 'nanoassert'
36

47
/**
58
* Returns `true` if all elements of an iterable pass a truth test and `false` otherwise.
69
*
7-
* The iteratee will be run in parallel. If any truth test returns `false` the promise is immediately resolved.
10+
* The calls to `iteratee` will be performed in a queue to limit the concurrency of these calls.
11+
* If any truth test returns `false` the promise is immediately resolved.
812
*
9-
* In case of exception in one of the iteratee calls the promise returned by this function will be rejected
10-
* with the exception. In the very specific case where a test returns `false` and an already started task throws
11-
* an exception that exception will be plainly ignored.
13+
* Whenever a test returns `false`, all the remaining tasks will be cancelled as long
14+
* as they didn't started already. In case of exception in one of the iteratee calls the promise
15+
* returned by this function will be rejected with the exception and the remaining pending
16+
* tasks will also be cancelled. In the very specific case where a test returns `false` and an
17+
* already started task throws an exception that exception will be plainly ignored.
1218
*
1319
* @param {Iterable | AsyncIterable} iterable An iterable or async iterable object.
1420
* @param {Function} iteratee A function that will be called with each member of the iterable. It will receive
1521
* three arguments:
1622
* * `value`: The current value to process
1723
* * `index`: The index in the iterable. Will start from 0.
1824
* * `iterable`: The iterable on which the operation is being performed.
25+
* @param {Queue | number} queueOrConcurrency If a queue is specified it will be used to schedule the calls to
26+
* `iteratee`. If a number is specified it will be used as the concurrency of a Queue that will be created
27+
* implicitly for the same purpose. Defaults to `1`.
1928
* @returns {Promise<boolean>} A promise that will be resolved to `true` if all values pass the truth test and `false`
2029
* if a least one of them doesn't pass it. That promise will be rejected if one of the truth test throws
2130
* an exception.
@@ -25,15 +34,22 @@ import everyLimit from './everyLimit.mjs'
2534
* const array = [1, 2, 3]
2635
*
2736
* const result = await every(array, async (v) => {
28-
* // these calls will be performed in parallel
37+
* // these calls will be performed in parallel with a maximum of 2
38+
* // concurrent calls
2939
* await sleep(10) // waits 10ms
3040
* return v > 0
31-
* })
41+
* }, 2)
3242
* console.log(result) // prints true
33-
* // total processing time should be ~ 10ms
43+
* // total processing time should be ~ 20ms
3444
*/
35-
async function every (iterable, iteratee) {
36-
return everyLimit(iterable, iteratee, Number.POSITIVE_INFINITY)
45+
async function every (iterable, iteratee, queueOrConcurrency = 1) {
46+
assert(typeof iteratee === 'function', 'iteratee must be a function')
47+
iteratee = asyncWrap(iteratee)
48+
const index = await findIndexLimit(iterable, async (value, index, iterable) => {
49+
return !(await iteratee(value, index, iterable))
50+
}, queueOrConcurrency, false)
51+
const result = index === -1
52+
return result
3753
}
3854

3955
export default every

src/every.test.mjs

Lines changed: 192 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,123 @@ import delay from './delay.mjs'
88
// eslint-disable-next-line require-jsdoc
99
class TestError extends Error {}
1010

11-
test('every all pass', async () => {
11+
test('every compatibility', async () => {
12+
let d = new Deferred()
13+
let p = every([...range(3)], async (v) => {
14+
await d.promise
15+
return true
16+
}, 1)
17+
d.resolve()
18+
expect(await p).toBe([...range(3)].every((v) => true))
19+
20+
d = new Deferred()
21+
p = every([...range(3)], async (v) => {
22+
await d.promise
23+
return v !== 2
24+
}, 1)
25+
d.resolve()
26+
expect(await p).toBe([...range(3)].every((v) => v !== 2))
27+
28+
d = new Deferred()
29+
p = every([...range(3)], async (v) => {
30+
await d.promise
31+
return false
32+
}, 1)
33+
d.resolve()
34+
expect(await p).toBe([...range(3)].every((v) => false))
35+
36+
d = new Deferred()
37+
p = every([], async (v) => {
38+
await d.promise
39+
return false
40+
}, 1)
41+
d.resolve()
42+
expect(await p).toBe([].every((v) => false))
43+
44+
d = new Deferred()
45+
p = every([], async (v) => {
46+
await d.promise
47+
return true
48+
}, 1)
49+
d.resolve()
50+
expect(await p).toBe([].every((v) => true))
51+
})
52+
53+
test('every parallel', async () => {
54+
let d = new Deferred()
55+
let p = every([...range(3)], async (v) => {
56+
await d.promise
57+
return true
58+
}, 10)
59+
d.resolve()
60+
expect(await p).toBe([...range(3)].every((v) => true))
61+
62+
d = new Deferred()
63+
p = every([...range(3)], async (v) => {
64+
await d.promise
65+
return v !== 2
66+
}, 10)
67+
d.resolve()
68+
expect(await p).toBe([...range(3)].every((v) => v !== 2))
69+
70+
d = new Deferred()
71+
p = every([...range(3)], async (v) => {
72+
await d.promise
73+
return false
74+
}, 10)
75+
d.resolve()
76+
expect(await p).toBe([...range(3)].every((v) => false))
77+
78+
d = new Deferred()
79+
p = every([], async (v) => {
80+
await d.promise
81+
return false
82+
}, 10)
83+
d.resolve()
84+
expect(await p).toBe([].every((v) => false))
85+
86+
d = new Deferred()
87+
p = every([], async (v) => {
88+
await d.promise
89+
return true
90+
}, 10)
91+
d.resolve()
92+
expect(await p).toBe([].every((v) => true))
93+
})
94+
95+
test('every first in time', async () => {
96+
const ds = [...range(3)].map(() => new Deferred())
97+
const p = every(range(3), async (v, i) => {
98+
await ds[i]
99+
return false
100+
}, 3)
101+
ds[2].resolve()
102+
const res = await p
103+
expect(res).toBe(false)
104+
})
105+
106+
test('every error', async () => {
107+
const callList = [...range(3)].map(() => 0)
108+
const p = every([...range(3)], async (v, i) => {
109+
callList[i] += 1
110+
if (i === 1) {
111+
throw new TestError()
112+
}
113+
return true
114+
}, 1)
115+
try {
116+
await p
117+
expect(true).toStrictEqual(false)
118+
} catch (e) {
119+
expect(e).toBeInstanceOf(TestError)
120+
}
121+
await delay()
122+
expect(callList[0]).toStrictEqual(1)
123+
expect(callList[1]).toStrictEqual(1)
124+
expect(callList[2]).toStrictEqual(0)
125+
})
126+
127+
test('every infinite concurrency all pass', async () => {
12128
const callCount = {}
13129
;[...range(3)].forEach((i) => { callCount[i] = 0 })
14130
const d = new Deferred()
@@ -18,7 +134,7 @@ test('every all pass', async () => {
18134
ds[i].resolve()
19135
await d.promise
20136
return true
21-
})
137+
}, Number.POSITIVE_INFINITY)
22138
await ds[2].promise
23139
expect(callCount[0]).toBe(1)
24140
expect(callCount[1]).toBe(1)
@@ -31,7 +147,7 @@ test('every all pass', async () => {
31147
expect(callCount[2]).toBe(1)
32148
})
33149

34-
test('every no all pass', async () => {
150+
test('every infinite concurrency no all pass', async () => {
35151
const callCount = {}
36152
;[...range(3)].forEach((i) => { callCount[i] = 0 })
37153
const d = new Deferred()
@@ -45,7 +161,7 @@ test('every no all pass', async () => {
45161
} else {
46162
return true
47163
}
48-
})
164+
}, Number.POSITIVE_INFINITY)
49165
await ds[2].promise
50166
expect(callCount[0]).toBe(1)
51167
expect(callCount[1]).toBe(1)
@@ -58,19 +174,90 @@ test('every no all pass', async () => {
58174
expect(callCount[2]).toBe(1)
59175
})
60176

61-
test('every error', async () => {
177+
test('every infinite concurrency error', async () => {
62178
const p = every([...range(3)], async (v, i) => {
63179
if (i === 1) {
64180
throw new TestError()
65181
}
66182
return true
183+
}, Number.POSITIVE_INFINITY)
184+
185+
try {
186+
await p
187+
expect(true).toStrictEqual(false)
188+
} catch (e) {
189+
expect(e).toBeInstanceOf(TestError)
190+
}
191+
await delay()
192+
})
193+
194+
test('every concurrency 1 all pass', async () => {
195+
const callCount = {}
196+
;[...range(3)].forEach((i) => { callCount[i] = 0 })
197+
const d = new Deferred()
198+
const ds = [...range(3)].map(() => new Deferred())
199+
const p = every([...range(3)], async (v, i) => {
200+
callCount[i] += 1
201+
ds[i].resolve()
202+
await d.promise
203+
return true
67204
})
205+
await ds[0].promise
206+
expect(callCount[0]).toBe(1)
207+
expect(callCount[1]).toBe(0)
208+
expect(callCount[2]).toBe(0)
209+
d.resolve()
210+
const res = await p
211+
expect(res).toBe(true)
212+
expect(callCount[0]).toBe(1)
213+
expect(callCount[1]).toBe(1)
214+
expect(callCount[2]).toBe(1)
215+
})
68216

217+
test('every concurrency 1 no all pass', async () => {
218+
const callCount = {}
219+
;[...range(3)].forEach((i) => { callCount[i] = 0 })
220+
const d = new Deferred()
221+
const ds = [...range(3)].map(() => new Deferred())
222+
const p = every([...range(3)], async (v, i) => {
223+
callCount[i] += 1
224+
ds[i].resolve()
225+
await d.promise
226+
if (i === 1) {
227+
return false
228+
} else {
229+
return true
230+
}
231+
})
232+
await ds[0].promise
233+
expect(callCount[0]).toBe(1)
234+
expect(callCount[1]).toBe(0)
235+
expect(callCount[2]).toBe(0)
236+
d.resolve()
237+
const res = await p
238+
expect(res).toBe(false)
239+
expect(callCount[0]).toBe(1)
240+
expect(callCount[1]).toBe(1)
241+
expect(callCount[2]).toBe(0)
242+
})
243+
244+
test('every concurrency 1 error', async () => {
245+
const callList = [...range(3)].map(() => 0)
246+
const p = every([...range(3)], async (v, i) => {
247+
callList[i] += 1
248+
if (i === 1) {
249+
throw new TestError()
250+
}
251+
return true
252+
})
69253
try {
70254
await p
71255
expect(true).toStrictEqual(false)
72256
} catch (e) {
73257
expect(e).toBeInstanceOf(TestError)
74258
}
75259
await delay()
260+
expect(callList[0]).toStrictEqual(1)
261+
expect(callList[1]).toStrictEqual(1)
262+
expect(callList[2]).toStrictEqual(0)
76263
})

src/everyLimit.mjs

Lines changed: 0 additions & 55 deletions
This file was deleted.

0 commit comments

Comments
 (0)