Skip to content

Instantly share code, notes, and snippets.

@Schniz
Created July 15, 2025 06:31
Show Gist options
  • Save Schniz/c4e855305bd33efc9722b857274ee6c0 to your computer and use it in GitHub Desktop.
Save Schniz/c4e855305bd33efc9722b857274ee6c0 to your computer and use it in GitHub Desktop.
/**
* Something that can be yielded
*/
interface Yieldable<T> {
(signal: AbortSignal): Promise<T>;
}
/**
* Executes a generator function that yields promises, allowing for cancellation via an AbortSignal.
*/
export async function co<B, C>(
signal: AbortSignal,
fn: () => Generator<Yieldable<any>, B, C>
): Promise<B> {
const gen = fn();
let current = gen.next();
while (!signal.aborted && !current.done) {
const v = await current.value(signal);
if (signal.aborted) {
current = gen.throw(signal.reason);
} else {
current = gen.next(v);
}
}
signal.throwIfAborted();
if (!current.done) {
throw new UnfinishedGeneratorError();
}
return current.value;
}
class UnfinishedGeneratorError extends Error {
name = 'UnfinishedGeneratorError';
constructor() {
super('unfinished generator');
}
}
export function* cancelable<T>(fn: Yieldable<T>): Generator<typeof fn, T, T> {
return yield fn;
}
export function* from<T>(promise: Promise<T>) {
return yield* cancelable(() => promise);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment