Skip to content

Instantly share code, notes, and snippets.

@gvergnaud
Last active February 13, 2026 21:32
Show Gist options
  • Select an option

  • Save gvergnaud/6675c07d0abab3ddbc831ed18e23ce8c to your computer and use it in GitHub Desktop.

Select an option

Save gvergnaud/6675c07d0abab3ddbc831ed18e23ce8c to your computer and use it in GitHub Desktop.
/**
* This function ensures there's a single instance of a given effectful function
* running at a time. If the function is called while it's already running,
* the arguments are combined and the function is called again with the combined
* arguments.
*/
export function oneAtATime<Args extends readonly unknown[], Return>(
fn: (...args: Args) => Promise<Return>,
combineArgs: (args: Args, nextArgs: Args) => Args = (_, newArgs) => newArgs,
): (...args: Args) => Promise<Return> {
let state:
| { type: "running" }
| {
type: "pending";
nextArgs: Args;
promiseResolvers: PromiseWithResolvers<Return>;
}
| { type: "idle" } = { type: "idle" };
const onFinally = () => {
if (state.type === "idle") {
return;
}
if (state.type === "running") {
state = { type: "idle" };
return;
}
const { promiseResolvers, nextArgs } = state;
state = { type: "running" };
void Promise.resolve()
.then(() => fn(...nextArgs))
.then(promiseResolvers.resolve, promiseResolvers.reject)
.finally(onFinally);
};
return (...args): Promise<Return> => {
if (state.type === "pending") {
state.nextArgs = combineArgs(state.nextArgs, args);
return state.promiseResolvers.promise;
}
if (state.type === "running") {
state = {
type: "pending",
nextArgs: args,
promiseResolvers: Promise.withResolvers<Return>(),
};
return state.promiseResolvers.promise;
}
state = { type: "running" };
return Promise.resolve()
.then(() => fn(...args))
.finally(onFinally);
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment