Skip to content

Commit

Permalink
Renamed some*
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolas-van committed Dec 28, 2023
1 parent 89cfc93 commit cb6e02b
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 251 deletions.
14 changes: 2 additions & 12 deletions modern-async.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,18 +145,10 @@ declare module "sleepPrecise" {
export default sleepPrecise;
function sleepPrecise(amount: number): Promise<void>;
}
declare module "someLimit" {
export default someLimit;
function someLimit<V>(iterable: Iterable<V> | AsyncIterable<V>, iteratee: (value: V, index: number, iterable: Iterable<V> | AsyncIterable<V>) => Promise<boolean> | boolean, queueOrConcurrency: Queue | number): Promise<boolean>;
import Queue from "Queue";
}
declare module "some" {
export default some;
function some<V>(iterable: Iterable<V> | AsyncIterable<V>, iteratee: (value: V, index: number, iterable: Iterable<V> | AsyncIterable<V>) => Promise<boolean> | boolean): Promise<boolean>;
}
declare module "someSeries" {
export default someSeries;
function someSeries<V>(iterable: Iterable<V> | AsyncIterable<V>, iteratee: (value: V, index: number, iterable: Iterable<V> | AsyncIterable<V>) => Promise<boolean> | boolean): Promise<boolean>;
function some<V>(iterable: Iterable<V> | AsyncIterable<V>, iteratee: (value: V, index: number, iterable: Iterable<V> | AsyncIterable<V>) => Promise<boolean> | boolean, queueOrConcurrency?: Queue | number): Promise<boolean>;
import Queue from "Queue";
}
declare module "TimeoutError" {
export default TimeoutError;
Expand Down Expand Up @@ -203,8 +195,6 @@ declare module "modern-async" {
export { default as sleepPrecise } from "sleepPrecise";
export { default as sleepPreciseCancellable } from "sleepPreciseCancellable";
export { default as some } from "some";
export { default as someLimit } from "someLimit";
export { default as someSeries } from "someSeries";
export { default as timeout } from "timeout";
export { default as TimeoutError } from "TimeoutError";
export { default as timeoutPrecise } from "timeoutPrecise";
Expand Down
2 changes: 0 additions & 2 deletions src/modern-async.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ export { default as sleepCancellable } from './sleepCancellable.mjs'
export { default as sleepPrecise } from './sleepPrecise.mjs'
export { default as sleepPreciseCancellable } from './sleepPreciseCancellable.mjs'
export { default as some } from './some.mjs'
export { default as someLimit } from './someLimit.mjs'
export { default as someSeries } from './someSeries.mjs'
export { default as timeout } from './timeout.mjs'
export { default as TimeoutError } from './TimeoutError.mjs'
export { default as timeoutPrecise } from './timeoutPrecise.mjs'
Expand Down
28 changes: 19 additions & 9 deletions src/some.mjs
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@

import someLimit from './someLimit.mjs'
import findIndex from './findIndex.mjs'
import Queue from './Queue.mjs'

/**
* Returns `true` if at least one element of an iterable pass a truth test and `false` otherwise.
*
* The calls to `iteratee` will run in parallel. If any truth test returns `true` the promise is immediately resolved.
* The calls to `iteratee` will be performed in a queue to limit the concurrency of these calls. If any
* truth test returns `true` the promise is immediately resolved.
*
* In case of exception in one of the `iteratee` calls the promise returned by this function will be rejected
* with the exception. In the very specific case where a test returns `true` and an already started task throws
* an exception that exception will be plainly ignored.
* Whenever a test returns `true`, all the remaining tasks will be cancelled as long
* as they didn't started already. In case of exception in one of the `iteratee` calls the promise
* returned by this function will be rejected with the exception and the remaining pending
* tasks will also be cancelled. In the very specific case where a test returns `true` and an
* already started task throws an exception that exception will be plainly ignored.
*
* @param {Iterable | AsyncIterable} iterable An iterable or async iterable object.
* @param {Function} iteratee A function that will be called with each member of the iterable. It will receive
* three arguments:
* * `value`: The current value to process
* * `index`: The index in the iterable. Will start from 0.
* * `iterable`: The iterable on which the operation is being performed.
* @param {Queue | number} queueOrConcurrency If a queue is specified it will be used to schedule the calls to
* `iteratee`. If a number is specified it will be used as the concurrency of a Queue that will be created
* implicitly for the same purpose. Defaults to `1`.
* @returns {Promise<boolean>} A promise that will be resolved to `true` if at least one value pass the truth test and `false`
* if none of them do. That promise will be rejected if one of the truth test throws an exception.
* @example
Expand All @@ -24,15 +31,18 @@ import someLimit from './someLimit.mjs'
* const array = [1, 2, 3]
*
* const result = await some(array, async (v) => {
* // these calls will be performed in parallel
* // these calls will be performed in parallel with a maximum of 2
* // concurrent calls
* await sleep(10) // waits 10ms
* return v % 2 === 0
* })
* }, 2)
* console.log(result) // prints true
* // total processing time should be ~ 10ms
*/
async function some (iterable, iteratee) {
return someLimit(iterable, iteratee, Number.POSITIVE_INFINITY)
async function some (iterable, iteratee, queueOrConcurrency = 1) {
const index = await findIndex(iterable, iteratee, queueOrConcurrency, false)
const result = index !== -1
return result
}

export default some
137 changes: 132 additions & 5 deletions src/some.test.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,87 @@

import { expect, test } from '@jest/globals'
import some from './some.mjs'
import Deferred from './Deferred.mjs'
import { range } from 'itertools'
import Deferred from './Deferred.mjs'

test('some compatibility', async () => {
const p = Promise.resolve()
let res = await some([...range(3)], async (v) => {
await p
return true
}, 1)
expect(res).toBe([...range(3)].some((v) => true))

test('some all no pass', async () => {
res = await some([...range(3)], async (v) => {
await p
return v !== 2
}, 1)
expect(res).toBe([...range(3)].some((v) => v !== 2))

res = await some([...range(3)], async (v) => {
await p
return false
}, 1)
expect(res).toBe([...range(3)].some((v) => false))

res = await some([], async (v) => {
await p
return false
}, 1)
expect(res).toBe([].some((v) => false))

res = await some([], async (v) => {
await p
return true
}, 1)
expect(res).toBe([].some((v) => true))
})

test('some parallel', async () => {
const p = Promise.resolve()
let res = await some([...range(3)], async (v) => {
await p
return true
}, 10)
expect(res).toBe([...range(3)].some((v) => true))

res = await some([...range(3)], async (v) => {
await p
return v !== 2
}, 10)
expect(res).toBe([...range(3)].some((v) => v !== 2))

res = await some([...range(3)], async (v) => {
await p
return false
}, 10)
expect(res).toBe([...range(3)].some((v) => false))

res = await some([], async (v) => {
await p
return false
}, 10)
expect(res).toBe([].some((v) => false))

res = await some([], async (v) => {
await p
return true
}, 10)
expect(res).toBe([].some((v) => true))
})

test('some first in time', async () => {
const ds = [...range(3)].map(() => new Deferred())
const p = some(range(3), async (v, i) => {
await ds[i]
return true
}, 3)
ds[2].resolve()
const res = await p
expect(res).toBe(true)
})

test('some infinite concurrency all no pass', async () => {
const callCount = {}
;[...range(3)].forEach((i) => { callCount[i] = 0 })
const d = new Deferred()
Expand All @@ -14,7 +91,7 @@ test('some all no pass', async () => {
ds[i].resolve()
await d.promise
return false
})
}, Number.POSITIVE_INFINITY)
await ds[2].promise
expect(callCount[0]).toBe(1)
expect(callCount[1]).toBe(1)
Expand All @@ -27,7 +104,7 @@ test('some all no pass', async () => {
expect(callCount[2]).toBe(1)
})

test('some some pass', async () => {
test('some infinite concurrency some pass', async () => {
const callCount = {}
;[...range(3)].forEach((i) => { callCount[i] = 0 })
const d = new Deferred()
Expand All @@ -41,7 +118,7 @@ test('some some pass', async () => {
} else {
return false
}
})
}, Number.POSITIVE_INFINITY)
await ds[2].promise
expect(callCount[0]).toBe(1)
expect(callCount[1]).toBe(1)
Expand All @@ -53,3 +130,53 @@ test('some some pass', async () => {
expect(callCount[1]).toBe(1)
expect(callCount[2]).toBe(1)
})

test('some concurrency 1 all no pass', async () => {
const callCount = {}
;[...range(3)].forEach((i) => { callCount[i] = 0 })
const d = new Deferred()
const ds = [...range(3)].map(() => new Deferred())
const p = some([...range(3)], async (v, i) => {
callCount[i] += 1
ds[i].resolve()
await d.promise
return false
})
await ds[0].promise
expect(callCount[0]).toBe(1)
expect(callCount[1]).toBe(0)
expect(callCount[2]).toBe(0)
d.resolve()
const res = await p
expect(res).toBe(false)
expect(callCount[0]).toBe(1)
expect(callCount[1]).toBe(1)
expect(callCount[2]).toBe(1)
})

test('some concurrency 1 some pass', async () => {
const callCount = {}
;[...range(3)].forEach((i) => { callCount[i] = 0 })
const d = new Deferred()
const ds = [...range(3)].map(() => new Deferred())
const p = some([...range(3)], async (v, i) => {
callCount[i] += 1
ds[i].resolve()
await d.promise
if (i === 1) {
return true
} else {
return false
}
})
await ds[0].promise
expect(callCount[0]).toBe(1)
expect(callCount[1]).toBe(0)
expect(callCount[2]).toBe(0)
d.resolve()
const res = await p
expect(res).toBe(true)
expect(callCount[0]).toBe(1)
expect(callCount[1]).toBe(1)
expect(callCount[2]).toBe(0)
})
48 changes: 0 additions & 48 deletions src/someLimit.mjs

This file was deleted.

82 changes: 0 additions & 82 deletions src/someLimit.test.mjs

This file was deleted.

Loading

0 comments on commit cb6e02b

Please sign in to comment.