Skip to content

Instantly share code, notes, and snippets.

@panoply
Created March 9, 2025 22:41
Show Gist options
  • Save panoply/22ed870c794e8a7f388a69f41653de1c to your computer and use it in GitHub Desktop.
Save panoply/22ed870c794e8a7f388a69f41653de1c to your computer and use it in GitHub Desktop.
Piper
/**
* Interface for the chainable object returned by the `pipe` function.
* It provides methods to control the execution flow of tasks, including
* stopping the chain based on conditions, handling conditional task execution,
* and supporting Promise-like behavior for asynchronous operations.
*/
export interface PipeChain {
/**
* Stops the pipe chain if the provided condition is `true`.
*
* When the condition resolves to `true`, the execution of subsequent tasks
* in the chain is cancelled, and no further operations will run.
*
* @param condition - A boolean value that determines whether to stop the chain.
* @returns A function that accepts additional tasks to execute if the chain continues.
*
* @example
* pipe(
* () => console.log("Step 1"),
* () => console.log("Step 2")
* ).stop(true)(
* // This task will not execute because the condition is true
* () => console.log("Step 3")
* );
* // Output: "Step 1", "Step 2"
*/
stop(condition: boolean): (...tasks: [ Task[] ] | Task[]) => PipeChain;
/**
* Executes tasks conditionally based on the provided condition.
*
* If the condition is `true`, the tasks in the first argument (`ifTrue`) will run.
* If the condition is `false`, the tasks in the optional second argument (`ifFalse`)
* will run instead. If no `ifFalse` tasks are provided, the chain continues without
* executing any tasks for the false case.
*
* @param condition - A boolean value that determines which branch to execute.
* @returns A function that accepts tasks for the `true` case, returning a `PipeChain`
* with an optional second call for the `false` case.
*
* @example
* pipe(
* () => console.log("Start")
* ).next(true)(
* // Runs when condition is true
* () => console.log("Condition was true")
* )(
* // Runs when condition is false (not executed in this case)
* () => console.log("Condition was false")
* );
* // Output: "Start", "Condition was true"
*
* pipe(
* () => console.log("Start")
* ).next(false)(
* () => console.log("Condition was true")
* )(
* () => console.log("Condition was false")
* );
* // Output: "Start", "Condition was false"
*/
next(condition: boolean): (ifTrue: Task | Task[]) => PipeChain & { (ifFalse: Task | Task[]): PipeChain };
/**
* Attaches callbacks for handling the resolution or rejection of the pipe chain.
*
* This method makes the `PipeChain` Promise-like, allowing you to handle the
* result of asynchronous tasks once they complete.
*
* @param onfulfilled - The callback to execute when the chain resolves successfully.
* @param onrejected - The callback to execute if the chain encounters an error.
* @returns A Promise that resolves with the result of the callback.
*
* @example
* pipe(
* () => Promise.resolve("Success")
* ).then(
* (value) => console.log(value), // Outputs: "Success"
* (error) => console.error(error)
* );
*/
then: <TResult = void, TError = never>(
onfulfilled?: ((value: void) => TResult | PromiseLike<TResult>) | undefined | null,
onrejected?: ((reason: any) => TError | PromiseLike<TError>) | undefined | null
) => PromiseLike<TResult>;
/**
* Attaches a callback to handle errors in the pipe chain.
*
* This method catches any rejections or errors that occur during the execution
* of the chain, similar to a Promise's `catch`.
*
* @param onrejected - The callback to execute if the chain rejects or encounters an error.
* @returns A Promise that resolves with the result of the error handler or the original value.
*
* @example
* pipe(
* () => Promise.reject("Error occurred")
* ).catch(
* (error) => console.log(error) // Outputs: "Error occurred"
* );
*/
catch: <TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null) => PromiseLike<void | TResult>;
/**
* Attaches a callback that runs when the pipe chain settles (either resolves or rejects).
*
* This method is useful for cleanup or final actions, similar to a Promise's `finally`.
*
* @param onfinally - The callback to execute when the chain settles.
* @returns A Promise that resolves once the callback completes.
*
* @example
* pipe(
* () => console.log("Running")
* ).finally(
* () => console.log("Done") // Outputs: "Running", "Done"
* );
*/
finally: (onfinally?: (() => void) | undefined | null) => PromiseLike<void>;
}
export function piper (...initialTasks: Task[]): PipeChain {
const stages: {
type: 'tasks' | 'true';
condition?: boolean;
tasks?: Task[];
ifTrue?: Task[];
ifFalse?: Task[];
}[] = [
{
type: 'tasks',
tasks: initialTasks
}
];
let isCancelled = false;
async function executeTasks (tasks: Task[]) {
for (const task of tasks) {
if (isCancelled) break;
const result = task();
await Promise.resolve(result);
}
}
const chain: PipeChain = {
stop (condition: boolean) {
return (...nextTasks: Task[] | [Task[]]) => {
if (!isCancelled) {
const tasks = Array.isArray(nextTasks[0]) && nextTasks.length === 1 ? nextTasks[0] : nextTasks as Task[];
stages.push({
type: 'tasks',
condition,
tasks
});
}
return chain;
};
},
next (condition: boolean) {
const trueHandler = (ifTrue: Task | Task[]) => {
if (!isCancelled) {
stages.push({
type: 'true',
condition,
ifTrue: Array.isArray(ifTrue) ? ifTrue : [ ifTrue ]
});
}
const chainWithOptionalFalse: PipeChain & { (ifFalse: Task | Task[]): PipeChain } = Object.assign(
function (ifFalse: Task | Task[]) {
if (!isCancelled && stages[stages.length - 1].type === 'true') {
stages[stages.length - 1].ifFalse = Array.isArray(ifFalse) ? ifFalse : [ ifFalse ];
}
return chain;
},
chain
);
return chainWithOptionalFalse;
};
return trueHandler;
},
then: null as any,
catch: null as any,
finally: null as any
};
const promise = (async () => {
for (const stage of stages) {
if (isCancelled) break;
switch (stage.type) {
case 'tasks':
if (stage.condition) {
isCancelled = true;
break;
}
await executeTasks(stage.tasks);
break;
case 'true':
if (stage.condition && stage.ifTrue) {
await executeTasks(stage.ifTrue);
} else if (!stage.condition && stage.ifFalse) {
await executeTasks(stage.ifFalse);
}
break;
}
}
})();
chain.then = promise.then.bind(promise);
chain.catch = promise.catch.bind(promise);
chain.finally = promise.finally.bind(promise);
return chain;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment