Created
August 31, 2023 09:47
-
-
Save Rowadz/87cbb414853baf7a511b088394f51994 to your computer and use it in GitHub Desktop.
race condition of promises with a stopping condition
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import axios, { AxiosResponse } from 'axios' | |
import { v4 as uuidv4 } from 'uuid' | |
export type PromiseStatus<T> = { | |
promise: Promise<T> | |
isFulfilled: boolean | |
isRejected: boolean | |
} | |
export type TODO = { | |
userId: number | |
id: number | |
title: string | |
completed: boolean | |
} | |
export type PromiseMap<T> = Map<string, PromiseStatus<T>> | |
declare global { | |
interface PromiseConstructor { | |
id: string | |
raceWithCondition: <T>( | |
promises: Promise<T>[], | |
fun: (value: T) => boolean | |
) => Promise<T> | |
} | |
} | |
Promise.raceWithCondition = async <T>( | |
promises: Promise<T>[], | |
fun: (value: T) => boolean | |
): Promise<T> => { | |
const map: PromiseMap<T> = new Map<string, PromiseStatus<T>>() | |
const mappedPromises: [string, Promise<T>][] = promises.map((p) => { | |
const id = uuidv4() | |
p = p | |
.then((value: T) => { | |
const obj = map.get(id)! | |
obj.isFulfilled = true | |
return value | |
}) | |
.catch((error: any) => { | |
const obj = map.get(id)! | |
obj.isRejected = true | |
return error | |
}) | |
map.set(id, { promise: p, isRejected: false, isFulfilled: false }) | |
return [id, p] | |
}) | |
const result = await Promise.race(mappedPromises.map(([_, p]) => p)) | |
if (!(result instanceof Error) && fun(result)) { | |
return result | |
} | |
const nextCallPromises: Promise<T>[] = mappedPromises | |
.filter(([id]) => { | |
const obj = map.get(id)! | |
return !(obj.isFulfilled || obj.isRejected) | |
}) | |
.map(([_, p]) => p) | |
return Promise.raceWithCondition(nextCallPromises, fun) | |
} | |
const DEFAULT_TIMEOUT = 200 | |
const fetchData = async (id: number): Promise<AxiosResponse<TODO>> => { | |
// simulate a delay | |
if (id === 1) { | |
await new Promise((resolve) => setTimeout(resolve, DEFAULT_TIMEOUT + 1)) | |
} | |
// simulate an error | |
if (id === 2) { | |
throw new Error('') | |
} | |
// simulate a rejection in the promise | |
if (id === 3) { | |
return Promise.reject('') | |
} | |
return axios.get<TODO>(`https://jsonplaceholder.typicode.com/todos/${id}`) | |
} | |
const main = async () => { | |
const promises = [ | |
fetchData(1), | |
fetchData(2), | |
fetchData(3), | |
fetchData(4), | |
fetchData(5), | |
fetchData(6), | |
] | |
const finalData = await Promise.raceWithCondition<AxiosResponse<TODO>>( | |
promises, | |
(value: AxiosResponse<TODO>) => { | |
return value && value.data.id === 1 | |
} | |
).then((response) => response.data) | |
return finalData | |
} | |
main().then(console.log) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment