Last active
February 7, 2022 17:02
-
-
Save Phryxia/a21f430ade7d4796ef3f71dc50005421 to your computer and use it in GitHub Desktop.
Custom react hook for executing batch promises and retrieve its result in asynchronous way with minimum rendering.
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 { useEffect, useReducer, useRef, useState } from 'react' | |
interface DialationState { | |
counter: number | |
called: number | |
resolved: number | |
timer?: NodeJS.Timeout | |
isCalling: boolean | |
} | |
const SEC_IN_MILLISECONDS = 1000 | |
// divide 'evaluators' and execute 'callsPerSecond' of them per second | |
// return object of which key is 'keyMaker(result)' | |
// isCalling: true if there are unevaluated promises | |
// isWaiting: true if there are unresolved promises | |
export function useDialatedPromises<T, K extends string | number | symbol>( | |
evaluators: (() => Promise<T>)[], | |
callsPerSecond: number, | |
keyMaker: (value: T) => K, | |
): { | |
currentResults: Partial<Record<K, T>> | |
abort: () => void | |
isCalling: boolean | |
isWaiting: boolean | |
} { | |
const [, update] = useReducer(() => ({}), {}) | |
const [results, setResults] = useState<Partial<Record<K, T>>>({}) | |
const dialationState = useRef<DialationState>({ | |
counter: 0, | |
called: 0, | |
resolved: 0, | |
isCalling: true, | |
}) | |
useEffect(() => { | |
function loop(): void { | |
const subEvaluators = evaluators.slice( | |
dialationState.current.counter, | |
dialationState.current.counter + callsPerSecond, | |
) | |
dialationState.current.called += subEvaluators.length | |
// you don't need to update when isCalling === true | |
if (!dialationState.current.isCalling) update() | |
subEvaluators.forEach(async evaluator => { | |
try { | |
const result = await evaluator() | |
dialationState.current.resolved += 1 | |
setResults(results => ({ ...results, [keyMaker(result)]: result })) | |
} catch { | |
dialationState.current.resolved += 1 | |
if (!dialationState.current.isCalling) update() | |
} | |
}) | |
if (dialationState.current.isCalling && dialationState.current.counter + callsPerSecond < evaluators.length) { | |
dialationState.current.counter += callsPerSecond | |
dialationState.current.timer = setTimeout(loop, SEC_IN_MILLISECONDS) | |
} else { | |
dialationState.current.isCalling = false | |
update() | |
} | |
} | |
loop() | |
return () => { | |
if (dialationState.current.timer) clearTimeout(dialationState.current.timer) | |
dialationState.current.counter = 0 | |
dialationState.current.timer = undefined | |
dialationState.current.isCalling = true | |
setResults({}) | |
} | |
}, [evaluators]) | |
function abort(): void { | |
if (dialationState.current.timer) clearTimeout(dialationState.current.timer) | |
dialationState.current.timer = undefined | |
dialationState.current.isCalling = false | |
update() | |
} | |
return { | |
currentResults: results, | |
abort, | |
isCalling: dialationState.current.isCalling, | |
isWaiting: dialationState.current.isCalling || dialationState.current.resolved < dialationState.current.called, | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here is the example