Skip to content

Instantly share code, notes, and snippets.

@yimity
Created April 15, 2026 04:21
Show Gist options
  • Select an option

  • Save yimity/59aa1a90ba5f33e9cd9618f952b2dee9 to your computer and use it in GitHub Desktop.

Select an option

Save yimity/59aa1a90ba5f33e9cd9618f952b2dee9 to your computer and use it in GitHub Desktop.
step-runner-demo.ts
type StepCallback<T = unknown> = () => T | Promise<T>;
interface StepRunner {
run<T>(id: string, callback: StepCallback<T>): Promise<T>;
}
interface PendingStep {
id: string;
callback: StepCallback;
}
async function execute(
fn: (step: StepRunner) => Promise<void> | void,
stepState: Map<string, unknown>
): Promise<boolean> {
let newStep: PendingStep | null = null;
// Run the user function in the background. It will hang at the new step
void fn({
run: async <T>(id: string, callback: StepCallback<T>): Promise<T> => {
// If this step already ran, return the memoized result
if (stepState.has(id)) {
return stepState.get(id) as T;
}
// This is a new step. Report it
newStep = { id, callback };
// Hang forever
return new Promise<T>(() => {});
},
});
// Schedule a macrotask. All pending microtasks (the resolved awaits from
// memoized steps) will drain before this runs, giving the workflow function
// time to advance through already-completed steps and reach the next new one.
await new Promise((r) => setTimeout(r, 0));
const pendingStep = newStep as PendingStep | null;
if (pendingStep !== null) {
// A new step was found. Execute it and save the result
const result = await pendingStep.callback();
stepState.set(pendingStep.id, result);
// Function is not done
return false;
}
// Function is done
return true;
}
// User-defined workflow function
async function myWorkflow(step: StepRunner): Promise<void> {
console.log(' Workflow: top');
const data = await step.run('fetch', () => {
console.log(' Step: fetch');
return [1, 2, 3];
});
console.log(' Workflow: fetch data: ', data);
const processed = await step.run('process', () => {
console.log(' Step: process');
return data.map((n) => n * 2);
});
console.log(' Workflow: process data: ', processed);
console.log(' Workflow: complete', processed);
}
async function main() {
// In-memory store of completed step results
const stepState = new Map<string, unknown>();
// Keep entering the workflow function until it's done
let done = false;
let i = 0;
while (!done) {
console.log(`Run ${i}:`);
done = await execute(myWorkflow, stepState);
console.log('--------------------------------');
i++;
}
}
main();
ref:https://www.inngest.com/blog/hanging-promises-for-control-flow
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment