A promise is a small state machine: a state, a value and a list of reactions. Settling stores the value and queues each reaction as a microtask. then() creates the next promise plus a reaction that feeds the handler's result into that promise's resolve, which is all chaining is. Resolving with a thenable (any object with a then method) subscribes to it instead of storing it.
class MyPromise {
#state = "pending"; #value; #reactions = [];
constructor(executor) {
let called = false; // resolve and reject work only once
const once = fn => arg => { if (!called) { called = true; fn(arg); } };
const reject = once(e => this.#settle("rejected", e));
try { executor(once(v => this.#adopt(v)), reject); } catch (err) { reject(err); }
}
#adopt(value) { // follow a thenable, or fulfill with the value
if (typeof value?.then !== "function") return this.#settle("fulfilled", value);
queueMicrotask(() => value.then(v => this.#adopt(v), e => this.#settle("rejected", e)));
}
#settle(state, value) {
if (this.#state !== "pending") return;
[this.#state, this.#value] = [state, value];
this.#reactions.forEach(queueMicrotask); // run waiting reactions as microtasks
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
const run = () => {
const ok = this.#state === "fulfilled", handler = ok ? onFulfilled : onRejected;
if (typeof handler !== "function") return (ok ? resolve : reject)(this.#value);
try { resolve(handler(this.#value)); } catch (err) { reject(err); }
};
this.#state === "pending" ? this.#reactions.push(run) : queueMicrotask(run);
});
}
catch(onRejected) { return this.then(undefined, onRejected); }
}
new MyPromise(resolve => setTimeout(resolve, 10, 21))
.then(n => new MyPromise(resolve => resolve(n * 2)))
.then(n => { throw new Error(`boom ${n}`); })
.catch(err => console.log("caught:", err.message));caught: boom 42
The native version also rejects a promise resolved with itself, catches a throwing then, and tracks unhandled rejections. The Promises/A+ test suite (https://github.com/promises-aplus/promises-tests 965 ) (npm 2,036 install promises-aplus-tests) checks an implementation against the rules.