Skip to content

Commit 15ff994

Browse files
committed
Renamed findIndex*
1 parent 211f73f commit 15ff994

10 files changed

+270
-365
lines changed

modern-async.d.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,6 @@ declare module "Queue" {
5858
cancelAllPending(): number;
5959
}
6060
}
61-
declare module "findIndexLimit" {
62-
export default findIndexLimit;
63-
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>;
64-
import Queue from "Queue";
65-
}
6661
declare module "every" {
6762
export default every;
6863
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>;
@@ -94,11 +89,8 @@ declare module "find" {
9489
}
9590
declare module "findIndex" {
9691
export default findIndex;
97-
function findIndex<V>(iterable: Iterable<V> | AsyncIterable<V>, iteratee: (value: V, index: number, iterable: Iterable<V> | AsyncIterable<V>) => Promise<boolean> | boolean, ordered?: boolean): Promise<number>;
98-
}
99-
declare module "findIndexSeries" {
100-
export default findIndexSeries;
101-
function findIndexSeries<V>(iterable: Iterable<V> | AsyncIterable<V>, iteratee: (value: V, index: number, iterable: Iterable<V> | AsyncIterable<V>) => Promise<boolean> | boolean): Promise<number>;
92+
function findIndex<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>;
93+
import Queue from "Queue";
10294
}
10395
declare module "forEachLimit" {
10496
export default forEachLimit;
@@ -214,8 +206,6 @@ declare module "modern-async" {
214206
export { default as filterGenerator } from "filterGenerator";
215207
export { default as find } from "find";
216208
export { default as findIndex } from "findIndex";
217-
export { default as findIndexLimit } from "findIndexLimit";
218-
export { default as findIndexSeries } from "findIndexSeries";
219209
export { default as forEach } from "forEach";
220210
export { default as forEachLimit } from "forEachLimit";
221211
export { default as forEachSeries } from "forEachSeries";

src/every.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11

2-
import findIndexLimit from './findIndexLimit.mjs'
32
import Queue from './Queue.mjs'
43
import asyncWrap from './asyncWrap.mjs'
54
import assert from 'nanoassert'
5+
import findIndex from './findIndex.mjs'
66

77
/**
88
* Returns `true` if all elements of an iterable pass a truth test and `false` otherwise.
@@ -45,7 +45,7 @@ import assert from 'nanoassert'
4545
async function every (iterable, iteratee, queueOrConcurrency = 1) {
4646
assert(typeof iteratee === 'function', 'iteratee must be a function')
4747
iteratee = asyncWrap(iteratee)
48-
const index = await findIndexLimit(iterable, async (value, index, iterable) => {
48+
const index = await findIndex(iterable, async (value, index, iterable) => {
4949
return !(await iteratee(value, index, iterable))
5050
}, queueOrConcurrency, false)
5151
const result = index === -1

src/findIndex.mjs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11

2-
import findIndexLimit from './findIndexLimit.mjs'
2+
import findInternal from './findInternal.mjs'
3+
import Queue from './Queue.mjs'
34

45
/**
56
* Returns the index of the first element of an iterable that passes an asynchronous truth test.
67
*
7-
* The calls to `iteratee` will run in parallel.
8+
* The calls to `iteratee` will be performed in a queue to limit the concurrency of these calls.
89
*
9-
* In case of exception in one of the `iteratee` calls the promise returned by this function will be
10-
* rejected with the exception. In the very specific case where a result is found and an
10+
* Whenever a result is found, all the remaining tasks will be cancelled as long
11+
* as they didn't started already. In case of exception in one of the iteratee calls the promise
12+
* returned by this function will be rejected with the exception and the remaining pending
13+
* tasks will also be cancelled. In the very specific case where a result is found and an
1114
* already started task throws an exception that exception will be plainly ignored.
1215
*
1316
* @param {Iterable | AsyncIterable} iterable An iterable or async iterable object.
@@ -16,23 +19,28 @@ import findIndexLimit from './findIndexLimit.mjs'
1619
* * `value`: The current value to process
1720
* * `index`: The index in the iterable. Will start from 0.
1821
* * `iterable`: The iterable on which the operation is being performed.
22+
* @param {Queue | number} queueOrConcurrency If a queue is specified it will be used to schedule the calls to
23+
* `iteratee`. If a number is specified it will be used as the concurrency of a Queue that will be created
24+
* implicitly for the same purpose. Defaults to `1`.
1925
* @param {boolean} [ordered] If true this function will return on the first element in the iterable
2026
* order for which `iteratee` returned true. If false it will be the first in time.
2127
* @returns {Promise<number>} A promise that will be resolved with the index of the first found value or rejected if one of the
2228
* `iteratee` calls throws an exception before finding a value. If no value is found it will return `-1`.
2329
* @example
2430
* import { findIndex, sleep } from 'modern-async'
2531
*
26-
* const array = [1, 2, 3]
32+
* const array = [1, 2, 3, 4, 5]
2733
* const result = await findIndex(array, async (v) => {
28-
* // these calls will be performed in parallel
34+
* // these calls will be performed in parallel with a maximum of 3
35+
* // concurrent calls
2936
* await sleep(Math.random() * 10) // waits a random amount of time between 0ms and 10ms
3037
* return v % 2 === 1
31-
* })
38+
* }, 3)
3239
* console.log(result) // prints 0
3340
*/
34-
async function findIndex (iterable, iteratee, ordered = false) {
35-
return findIndexLimit(iterable, iteratee, Number.POSITIVE_INFINITY, ordered)
41+
async function findIndex (iterable, iteratee, queueOrConcurrency = 1, ordered = false) {
42+
const result = (await findInternal(iterable, iteratee, queueOrConcurrency, ordered))[0]
43+
return result
3644
}
3745

3846
export default findIndex

src/findIndex.test.mjs

Lines changed: 247 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,35 @@ import { expect, test } from '@jest/globals'
33
import findIndex from './findIndex.mjs'
44
import Deferred from './Deferred.mjs'
55
import { range } from 'itertools'
6+
import delay from './delay.mjs'
67

7-
test('findIndex', async () => {
8+
test('findIndex compatibility', async () => {
9+
let d = new Deferred()
10+
let p = findIndex([...range(3)], async (v) => {
11+
await d.promise
12+
return v === 2
13+
}, 1)
14+
d.resolve()
15+
expect(await p).toBe([...range(3)].findIndex((v) => v === 2))
16+
17+
d = new Deferred()
18+
p = findIndex([...range(3)], async (v) => {
19+
await d.promise
20+
return v === 5
21+
}, 1)
22+
d.resolve()
23+
expect(await p).toBe([...range(3)].findIndex((v) => v === 5))
24+
25+
d = new Deferred()
26+
p = findIndex([], async (v) => {
27+
await d.promise
28+
return v === 5
29+
}, 1)
30+
d.resolve()
31+
expect(await p).toBe([].findIndex((v) => v === 5))
32+
})
33+
34+
test('findIndex cancel subsequent', async () => {
835
const callCount = {}
936
;[...range(3)].forEach((i) => { callCount[i] = 0 })
1037
const d = new Deferred()
@@ -14,7 +41,157 @@ test('findIndex', async () => {
1441
ds[i].resolve()
1542
await d.promise
1643
return v === 0
17-
})
44+
}, 1)
45+
await ds[0].promise
46+
expect(callCount[0]).toBe(1)
47+
expect(callCount[1]).toBe(0)
48+
expect(callCount[2]).toBe(0)
49+
d.resolve()
50+
await delay()
51+
const res = await p
52+
expect(res).toBe(0)
53+
expect(callCount[0]).toBe(1)
54+
expect(callCount[1]).toBe(0)
55+
expect(callCount[2]).toBe(0)
56+
})
57+
58+
test('findIndex cancel subsequent 2', async () => {
59+
const callCount = {}
60+
;[...range(6)].forEach((i) => { callCount[i] = 0 })
61+
const d = new Deferred()
62+
const ds = [...range(3)].map(() => new Deferred())
63+
const p = findIndex([...range(6)], async (v, i) => {
64+
callCount[i] += 1
65+
ds[i].resolve()
66+
await d.promise
67+
return v === 0
68+
}, 2)
69+
await ds[1].promise
70+
expect(callCount[0]).toBe(1)
71+
expect(callCount[1]).toBe(1)
72+
expect(callCount[2]).toBe(0)
73+
expect(callCount[3]).toBe(0)
74+
expect(callCount[4]).toBe(0)
75+
expect(callCount[5]).toBe(0)
76+
d.resolve()
77+
await delay()
78+
const res = await p
79+
expect(res === 0 || res === 1).toBe(true)
80+
expect(callCount[0]).toBe(1)
81+
expect(callCount[1]).toBe(1)
82+
expect(callCount[2]).toBe(0)
83+
expect(callCount[3]).toBe(0)
84+
expect(callCount[4]).toBe(0)
85+
expect(callCount[5]).toBe(0)
86+
})
87+
88+
test('findIndex find first in order', async () => {
89+
const arr = [0, 1, 0]
90+
let d1 = new Deferred()
91+
let d2 = new Deferred()
92+
let p = findIndex(arr, async (v, index) => {
93+
if (index === 0) {
94+
await d1.promise
95+
} else {
96+
await d2.promise
97+
}
98+
return v === 0
99+
}, 3, true)
100+
d1.resolve()
101+
await delay()
102+
d2.resolve()
103+
let res = await p
104+
expect(res).toBe(0)
105+
d2.resolve()
106+
107+
d1 = new Deferred()
108+
d2 = new Deferred()
109+
p = findIndex(arr, async (v, index) => {
110+
if (index === 0) {
111+
await d1.promise
112+
} else {
113+
await d2.promise
114+
}
115+
return v === 0
116+
}, 3, true)
117+
d2.resolve()
118+
await delay()
119+
d1.resolve()
120+
res = await p
121+
expect(res).toBe(0)
122+
d1.resolve()
123+
})
124+
125+
test('findIndex error', async () => {
126+
const arr = [0, 1, 2]
127+
try {
128+
const d = new Deferred()
129+
const p = findIndex(arr, async (v, index) => {
130+
if (index === 1) {
131+
throw new Error('test')
132+
} else {
133+
await d.promise
134+
}
135+
return v === 2
136+
}, 3)
137+
d.resolve()
138+
await p
139+
expect(false).toBe(true)
140+
} catch (e) {
141+
expect(e.message).toBe('test')
142+
}
143+
})
144+
145+
test('findIndex error after completion', async () => {
146+
const arr = [0, 1]
147+
const d1 = new Deferred()
148+
const d2 = new Deferred()
149+
const p = findIndex(arr, async (v, index) => {
150+
if (index === 0) {
151+
await d1.promise
152+
return true
153+
} else {
154+
await d2.promise
155+
throw new Error('should be ignored')
156+
}
157+
}, 2)
158+
d1.resolve()
159+
d2.resolve()
160+
const res = await p
161+
expect(res).toBe(0)
162+
})
163+
164+
test('findIndex concurrency', async () => {
165+
const callCount = {}
166+
;[...range(3)].forEach((i) => { callCount[i] = 0 })
167+
const d = new Deferred()
168+
const ds = [...range(3)].map(() => new Deferred())
169+
const p = findIndex([...range(10)], async (v, i) => {
170+
callCount[i] += 1
171+
ds[i].resolve()
172+
await d.promise
173+
return v === 1
174+
}, 3)
175+
await delay()
176+
expect(callCount[0]).toBe(1)
177+
expect(callCount[1]).toBe(1)
178+
expect(callCount[2]).toBe(1)
179+
d.resolve()
180+
const res = await p
181+
expect(res).toBe(1)
182+
})
183+
184+
test('findIndex infinite concurrency', async () => {
185+
const callCount = {}
186+
;[...range(3)].forEach((i) => { callCount[i] = 0 })
187+
const d = new Deferred()
188+
const ds = [...range(3)].map(() => new Deferred())
189+
const p = findIndex([...range(3)], async (v, i) => {
190+
callCount[i] += 1
191+
ds[i].resolve()
192+
await d.promise
193+
return v === 0
194+
}, Number.POSITIVE_INFINITY)
18195
await ds[2].promise
19196
expect(callCount[0]).toBe(1)
20197
expect(callCount[1]).toBe(1)
@@ -26,3 +203,71 @@ test('findIndex', async () => {
26203
expect(callCount[1]).toBe(1)
27204
expect(callCount[2]).toBe(1)
28205
})
206+
207+
test('findIndex concurrency 1', async () => {
208+
const callCount = {}
209+
;[...range(3)].forEach((i) => { callCount[i] = 0 })
210+
const d = new Deferred()
211+
const ds = [...range(3)].map(() => new Deferred())
212+
const p = findIndex([...range(3)], async (v, i) => {
213+
callCount[i] += 1
214+
ds[i].resolve()
215+
await d.promise
216+
return v === 0
217+
})
218+
await ds[0].promise
219+
expect(callCount[0]).toBe(1)
220+
expect(callCount[1]).toBe(0)
221+
expect(callCount[2]).toBe(0)
222+
d.resolve()
223+
const res = await p
224+
expect(res).toBe(0)
225+
expect(callCount[0]).toBe(1)
226+
expect(callCount[1]).toBe(0)
227+
expect(callCount[2]).toBe(0)
228+
})
229+
230+
test('findIndex concurrency 1 ordered', async () => {
231+
const callCount = {}
232+
;[...range(3)].forEach((i) => { callCount[i] = 0 })
233+
const d = new Deferred()
234+
const ds = [...range(3)].map(() => new Deferred())
235+
const p = findIndex([...range(3)], async (v, i) => {
236+
callCount[i] += 1
237+
ds[i].resolve()
238+
if (i === 0) {
239+
await d.promise
240+
}
241+
return true
242+
})
243+
await ds[0].promise
244+
expect(callCount[0]).toBe(1)
245+
expect(callCount[1]).toBe(0)
246+
expect(callCount[2]).toBe(0)
247+
d.resolve()
248+
const res = await p
249+
expect(res).toBe(0)
250+
expect(callCount[0]).toBe(1)
251+
expect(callCount[1]).toBe(0)
252+
expect(callCount[2]).toBe(0)
253+
})
254+
255+
test('findIndex concurrency 1 error', async () => {
256+
const callCount = {}
257+
;[...range(3)].forEach((i) => { callCount[i] = 0 })
258+
try {
259+
await findIndex([...range(3)], async (v, i) => {
260+
callCount[i] += 1
261+
if (i === 0) {
262+
throw new Error('test')
263+
}
264+
return true
265+
})
266+
expect(true).toBe(false)
267+
} catch (e) {
268+
expect(e.message).toBe('test')
269+
}
270+
expect(callCount[0]).toBe(1)
271+
expect(callCount[1]).toBe(0)
272+
expect(callCount[2]).toBe(0)
273+
})

0 commit comments

Comments
 (0)