Implement promiseMerge(p1, p2, merger). It takes two promises and a merger function, waits for both promises to fulfill, calls merger(value1, value2), and resolves with whatever the merger returns. If either input promise rejects, the result rejects with the first rejection reason — even if the other promise is still pending. This is the smallest useful brick in the world of promise composition: two parallel inputs, one combined output.
The point of the exercise is to internalize parallel waiting: both promises must be in flight at the same time, not chained one after the other. A naive .then chain looks like it works on small inputs and silently doubles your wall-clock time on real ones. You're also practicing the merger-as-projection pattern — separating the coordination of two async sources from the logic that combines their results — which is the building block behind Promise.all, Promise.race, and every async-fan-in helper in libraries like RxJS.
// p1, p2: promises (or plain values) whose results you want to combine.
// merger: pure function called with both fulfilled values, in (v1, v2) order.
// Returns a Promise that fulfils with merger's return value, or rejects with
// the first rejection reason from either input — or with merger's own error.
function promiseMerge<A, B, R>(
p1: A | PromiseLike<A>,
p2: B | PromiseLike<B>,
merger: (v1: A, v2: B) => R | PromiseLike<R>,
): Promise<R>;
// Basic addition. Both inputs fulfil, merger is called with (1, 2).
promiseMerge(Promise.resolve(1), Promise.resolve(2), (a, b) => a + b)
.then(console.log); // 3
// String concat. Merger projects two values into one string.
const user = Promise.resolve('Ada');
const greet = Promise.resolve('hello');
promiseMerge(user, greet, (u, g) => `${g}, ${u}`)
.then(console.log); // 'hello, Ada'
// First input rejects — result rejects immediately with that reason,
// even if p2 would have resolved later.
const bad = Promise.reject('boom');
const slow = new Promise((r) => setTimeout(() => r('late'), 100));
bad.catch(() => {});
promiseMerge(bad, slow, (a, b) => [a, b]).catch(console.log); // 'boom'
// Second input rejects first — its reason wins.
const slowOk = new Promise((r) => setTimeout(() => r('ok'), 50));
const fastBad = Promise.reject('fail');
fastBad.catch(() => {});
promiseMerge(slowOk, fastBad, (a, b) => a + b).catch(console.log); // 'fail'
p1 and then p2 is the wrong shape — it doubles the wall-clock wait.(value1, value2) matching the argument order, regardless of which promise settled first.merger throws synchronously or returns a rejected promise, the result rejects with that error. Don't swallow it.promiseMerge(1, 2, (a, b) => a + b) should work — wrap inputs with Promise.resolve.instanceof Promise. Don't return a thenable.