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.

Revisions

  1. Schniz created this gist Jul 15, 2025.
    47 changes: 47 additions & 0 deletions cancelable.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,47 @@
    /**
    * 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);
    }