Skip to content

Commit

Permalink
feat(trampoline): apply minimal defensive coding to generator iterati…
Browse files Browse the repository at this point in the history
…ons in trampoline
  • Loading branch information
naugtur committed Aug 27, 2024
1 parent 12f088d commit 6482568
Showing 1 changed file with 19 additions and 10 deletions.
29 changes: 19 additions & 10 deletions packages/trampoline/src/trampoline.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,34 @@
/**
* @import {AnyGenerator, SyncTrampolineGeneratorFn, AsyncTrampolineGeneratorFn } from './types.js'
*/
const { getPrototypeOf } = Object;
const { bind } = Function.prototype;
const uncurryThis = bind.bind(bind.call); // eslint-disable-line @endo/no-polymorphic-call
export const { prototype: generatorPrototype } = getPrototypeOf(
// eslint-disable-next-line no-empty-function, func-names
function* () {},
);
const generatorNext = uncurryThis(generatorPrototype.next);
const generatorThrow = uncurryThis(generatorPrototype.throw);

/**
* Trampoline on {@link TrampolineGeneratorFn generatorFn} synchronously.
*
* @template {readonly any[]} [TArgs=unknown[]] Parameters for `generatorFn`
* @template [TResult=unknown] Type of the return value of the `generatorFn`
* @template {Generator<any, any, TResult>} [TGenerator=Generator] Type of the generator function
* @param {SyncTrampolineGeneratorFn<TGenerator, TArgs, TResult>} generatorFn Generator-returning function accepting a thunk and optionally an initial value
* @param {TArgs} args Initial args
* @param {SyncTrampolineGeneratorFn<TGenerator, TArgs, TResult>} generatorFn Generator-returning function accepting any arguments
* @param {TArgs} args Arguments to pass to the generatorFn call
* @returns {TResult}
*/
export function syncTrampoline(generatorFn, ...args) {
const iterator = generatorFn(...args);
let result = iterator.next();
let result = generatorNext(iterator);
while (!result.done) {
try {
result = iterator.next(result.value);
result = generatorNext(iterator, result.value);
} catch (err) {
result = iterator.throw(err);
result = generatorThrow(iterator, err);
}
}
return result.value;
Expand All @@ -32,20 +41,20 @@ export function syncTrampoline(generatorFn, ...args) {
* @template {readonly any[]} [TArgs=unknown[]] Parameters for `generatorFn`
* @template [TResult=unknown] Type of the return value of the `generatorFn`
* @template {Generator<any, any, TResult>} [TGenerator=Generator] Type of the generator function
* @param {AsyncTrampolineGeneratorFn<TGenerator, TArgs, TResult>} generatorFn Generator-returning function accepting a thunk and optionally an initial value
* @param {TArgs} args Initial args
* @param {AsyncTrampolineGeneratorFn<TGenerator, TArgs, TResult>} generatorFn Generator-returning function accepting any arguments
* @param {TArgs} args Arguments to pass to the generatorFn call
* @returns {Promise<TResult>}
*/
export async function trampoline(generatorFn, ...args) {
const iterator = generatorFn(...args);
let result = iterator.next();
let result = generatorNext(iterator);
while (!result.done) {
try {
// eslint-disable-next-line no-await-in-loop
const val = await result.value;
result = iterator.next(val);
result = generatorNext(iterator, val);
} catch (err) {
result = iterator.throw(err);
result = generatorThrow(iterator, err);
}
}
return result.value;
Expand Down

0 comments on commit 6482568

Please sign in to comment.