A promise waterfall runs an array of async functions in sequence, passing each function's resolved result as the input to the next, and resolves with the value the last function returns. The name is the picture: a value falls from one task straight down into the next — never sideways — so only one task runs at a time. It is a well-known composition pattern; the popular async library ships it as async.waterfall.
type Task = (value: any) => any | Promise<any>;
// Runs tasks left-to-right: task[0] receives initialValue, and each later task
// receives the previous task's resolved value. Resolves with the final value.
function promiseWaterfall(tasks: Task[], initialValue: unknown): Promise<unknown>;
// Each task transforms the running value; the chain resolves with the last one.
promiseWaterfall([(n) => n + 1, (n) => n * 2, (n) => n - 3], 5)
.then(console.log); // 9 — ((5 + 1) * 2) - 3
// Tasks can be async. Each task waits for the previous promise to resolve.
const loadUser = (id) => Promise.resolve({ id, name: 'Ada' });
const nameOf = (user) => user.name.toUpperCase();
promiseWaterfall([loadUser, nameOf], 42).then(console.log); // 'ADA'
// An empty task list resolves with the initial value, untouched.
promiseWaterfall([], 'seed').then(console.log); // 'seed'
// A rejection stops the chain — later tasks never run.
promiseWaterfall([(n) => n + 1, () => Promise.reject('boom'), (n) => n * 2], 0)
.catch(console.log); // 'boom' (the * 2 task never runs)
Promise.all, which starts everything at once.task[i] receives whatever task[i - 1] resolved to. Only task[0] sees initialValue..then or await the result.