dataloader
dataloader11mo ago

Memory Leaks

Also, I'm reading through the rest of the docs. https://stack.convex.dev/throttling-requests-by-single-flighting#implementation-usesingleflight-callback this implementation is effectively an always running memory leak generator. It intentionally leaves promises forever unresolved!
8 Replies
ballingt
ballingt11mo ago
I remember having this worry too, but Ian showed me they don't, they get GC'd just like you'd hope!
ballingt
ballingt11mo ago
ballingt
ballingt11mo ago
Re that earlier infinite loop implementation, that's not how I'd write a single-flighter either (though I prefer its behavior of reporting the final resting cursor position after a drag instead of dropping them like your code does for this particular application) but it feels more effective at communicating the idea than the more Reacty or callback-based approaches I'd jump to I got too many code review notes from jashkenas to just return the promise instead of awaiting it in an async function or to avoid async when the idea can be expressed with .then callbacks, so that's what I do. But the erlang-like multiple concurrent processes thing is a slick way way to reason about this and I think communicates the idea better to some audiences — perhaps most!
dataloader
dataloaderOP11mo ago
Thanks for the detailed response. I made a quick npm package for this, which avoids the unnecessary loops and user-side dropped promises. IIUC it's the same https://github.com/nolanholden/react-dedup-async/blob/main/index.ts in short:
export function useDedupedAsyncCallback<P extends unknown[], R>(fn: (...args: P) => Promise<R>) {
const f = useLatestRef(fn);

let p: Promise<R> | null = null;
let latest: P;
return useRef(function wrapper(...args: P): Promise<R> {
latest = args;
return p
? p.then(() => p ?? wrapper(...latest))
: (p = f.current(...latest).finally(() => (p = null)));
}).current;
}
export function useDedupedAsyncCallback<P extends unknown[], R>(fn: (...args: P) => Promise<R>) {
const f = useLatestRef(fn);

let p: Promise<R> | null = null;
let latest: P;
return useRef(function wrapper(...args: P): Promise<R> {
latest = args;
return p
? p.then(() => p ?? wrapper(...latest))
: (p = f.current(...latest).finally(() => (p = null)));
}).current;
}
dataloader
dataloaderOP11mo ago
npm
react-dedup-async
Latest version: 0.1.0, last published: 10 minutes ago. Start using react-dedup-async in your project by running npm i react-dedup-async. There are no other projects in the npm registry using react-dedup-async.
ballingt
ballingt11mo ago
This is slick, I will use this in the future! I think I've written this logic before but it took me more lines of code 😆
dataloader
dataloaderOP11mo ago
It took some effort. Keeping track of what is binded where and when is a total mess lol
ballingt
ballingt11mo ago
nice to have a solid abstraction I can understand from the function name because each time I read it I have puzzle it out agin

Did you find this page helpful?