I came up with a solution that doesn't appear to be covered here, so I figured I'd share. I had the following requirements.
- Accepts an
async
function (or a function that returns a promise)
- Supplies
item
, index
, and array
to the function
- Returns the item with the fastest resolved promise (all are evaluated in parallel)
- Does not exit early if the
async
function rejects (throws an error)
- Supports generic types (in TypeScript)
With those requirements, I came up with the following solution using Promise.any
. It will resolve with the first fulfilled value or reject with an AggregateError
if none of the promises are fulfilled.
type Predicate<T> = (item: T, index: number, items: T[]) => Promise<boolean>
export const any = async <T>(array: T[], predicate: Predicate<T>): Promise<T> => {
return Promise.any(
array.map(async (item, index, items) => {
if (await predicate(item, index, items)) {
return item
}
throw new Error()
})
)
}
Now we can search an array in parallel with async
functions and return the fastest resolved result.
const things = [{ id: 0, ... }, { id: 1, ... }]
const found = await any(things, async (thing) => {
const otherThing = await getOtherThing()
return thing.id === otherThing.id
})
Enumerate the array sequentially
If we want to enumerate the array in sequence, as Array.find
does, then we can do that with some modifications.
export const first = async <T>(array: T[], predicate: Predicate<T>): Promise<T> => {
for (const [index, item] of array.entries()) {
try {
if (await predicate(item, index, array)) {
return item
}
} catch {
// If we encounter an error, keep searching.
}
}
// If we do not find any matches, "reject" by raising an error.
throw new Error()
}
Now, we can search an array in sequence and return the first resolved result.
const things = [{ id: 0, ... }, { id: 1, ... }]
const found = await first(things, async (thing) => {
const otherThing = await getOtherThing()
return thing.id === otherThing.id
})