// Promises, as the spec says, except: // - A throw in a handler or an executor does not reject, because there is no // `try`. `reject()` and a returned rejected promise do // - An unhandled rejection does nothing. Node prints it and stops // - `Promise.all` takes an array, not any iterable // // Written for type inference, which names an object by the instr that makes // it and the call place of the call it is made in (see compiler/src/infer): // - A promise is an object literal, made in the fn that user code calls, not // in a helper. So each place in user code gets its own promise objects, and // their fields are always there // - The closures that run a reaction are made in `then`, for the same reason // - Fulfilment and rejection have their own fns and fields, so that a result // and a reason never share a type // - A fn that makes a closure keeps out of the common path: a captured arg is // in a box, which every call from one place shares // The states of a promise const __PENDING = 0; const __FULFILLED = 1; const __REJECTED = 2; function Promise(executor) { const p = { __proto__: this.__proto__, state: __PENDING, value: undefined, reason: undefined, resolved: false, reactions: [] }; if (executor !== undefined) { executor((value) => __resolve(p, value), (reason) => __reject(p, reason)); } return p; } Promise.resolve = function (value) { if (__is_promise(value)) return value; const p = { __proto__: this.prototype, state: __PENDING, value: undefined, reason: undefined, resolved: false, reactions: [] }; __resolve(p, value); return p; }; Promise.reject = function (reason) { const p = { __proto__: this.prototype, state: __PENDING, value: undefined, reason: undefined, resolved: false, reactions: [] }; __reject(p, reason); return p; }; Promise.all = function (promises) { const p = { __proto__: this.prototype, state: __PENDING, value: undefined, reason: undefined, resolved: false, reactions: [] }; const values = []; let left = promises.length; if (left === 0) __resolve(p, values); for (let i = 0; i < promises.length; i++) { Promise.resolve(promises[i]).then( (value) => { values[i] = value; left--; if (left === 0) __resolve(p, values); }, (reason) => __reject(p, reason), ); } return p; }; Promise.prototype = { then(onFulfilled, onRejected) { const derived = { __proto__: this.__proto__, state: __PENDING, value: undefined, reason: undefined, resolved: false, reactions: [] }; let value; let reason; const fulfilled = () => { if (typeof onFulfilled === "function") __resolve(derived, onFulfilled(value)); else __resolve(derived, value); }; const rejected = () => { if (typeof onRejected === "function") __resolve(derived, onRejected(reason)); else __reject(derived, reason); }; // What `__fulfil()` and `__settle_rejected()` call: each runs its // handler in a job of its own const reaction = { fulfil: (v) => { value = v; __queue_job(fulfilled); }, reject: (r) => { reason = r; __queue_job(rejected); }, }; if (this.state === __PENDING) { this.reactions.push(reaction); } else if (this.state === __FULFILLED) { reaction.fulfil(this.value); } else { reaction.reject(this.reason); } return derived; }, catch(onRejected) { return this.then(undefined, onRejected); }, finally(f) { return this.then( (value) => { f(); return value; }, (reason) => { f(); return Promise.reject(reason); }, ); }, }; function __is_promise(x) { return typeof x === "object" && x !== null && x.reactions !== undefined; } // Resolve `p` with `value`, unless it was resolved or rejected before function __resolve(p, value) { if (p.resolved) return; p.resolved = true; __fulfil_or_adopt(p, value); } // Reject `p` with `reason`, unless it was resolved or rejected before function __reject(p, reason) { if (p.resolved) return; p.resolved = true; __settle_rejected(p, reason); } // Fulfil `p` with `value`, or if `value` is a thenable, adopt its state. That // is in a job of its own, as the spec says, so it takes a tick more function __fulfil_or_adopt(p, value) { if ((typeof value === "object" && value !== null) || typeof value === "function") { const then = value.then; if (typeof then === "function") { __queue_adopt(p, value); return; } } __fulfil(p, value); } // A closure boxes the args that it captures, and the box is one for every // call from one place. So only this fn captures `p` and `value`, and only // thenables share its boxes function __queue_adopt(p, thenable) { __queue_job(() => __adopt(p, thenable)); } // The reactions are cleared in place: a new array here would be one array // for every promise function __fulfil(p, value) { p.state = __FULFILLED; p.value = value; const reactions = p.reactions; for (let i = 0; i < reactions.length; i++) reactions[i].fulfil(value); reactions.length = 0; } function __settle_rejected(p, reason) { p.state = __REJECTED; p.reason = reason; const reactions = p.reactions; for (let i = 0; i < reactions.length; i++) reactions[i].reject(reason); reactions.length = 0; } // Make `p` follow the thenable `thenable` function __adopt(p, thenable) { thenable.then( (value) => { if (p.state === __PENDING) __fulfil_or_adopt(p, value); }, (reason) => { if (p.state === __PENDING) __settle_rejected(p, reason); }, ); } function queueMicrotask(f) { __queue_job(f); }