// 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 // The states of a promise const __PENDING = 0; const __FULFILLED = 1; const __REJECTED = 2; class Promise { constructor(executor) { this.state = __PENDING; this.value = undefined; this.resolved = false; // `resolve()` or `reject()` was called, maybe with a thenable this.reactions = []; if (executor !== undefined) { executor((value) => __resolve(this, value), (reason) => __reject(this, reason)); } } then(onFulfilled, onRejected) { const derived = new Promise(); const reaction = { onFulfilled: onFulfilled, onRejected: onRejected, derived: derived }; if (this.state === __PENDING) { this.reactions.push(reaction); } else { __queue_reaction(reaction, this.state, this.value); } return derived; } catch(onRejected) { return this.then(undefined, onRejected); } finally(f) { return this.then( (value) => { f(); return value; }, (reason) => { f(); return Promise.reject(reason); }, ); } static resolve(value) { if (__is_promise(value)) return value; const p = new Promise(); __resolve(p, value); return p; } static reject(reason) { const p = new Promise(); __reject(p, reason); return p; } static all(promises) { const p = new Promise(); 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; } } 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); } // 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_job(() => __adopt(p, value)); return; } } __settle(p, __FULFILLED, value); } function __reject(p, reason) { if (p.resolved) return; p.resolved = true; __settle(p, __REJECTED, reason); } function __settle(p, state, value) { p.state = state; p.value = value; const reactions = p.reactions; p.reactions = []; for (let i = 0; i < reactions.length; i++) { __queue_reaction(reactions[i], state, value); } } function __queue_reaction(reaction, state, value) { const handler = state === __FULFILLED ? reaction.onFulfilled : reaction.onRejected; const derived = reaction.derived; __queue_job(() => __react(handler, value, derived, state)); } // Call `handler` with the value of a promise that is in `state`, and resolve // `derived` with the result. With no handler, `derived` gets the same state function __react(handler, value, derived, state) { if (typeof handler === "function") { __resolve(derived, handler(value)); } else if (state === __FULFILLED) { __resolve(derived, value); } else { __reject(derived, value); } } // 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(p, __REJECTED, reason); }, ); } function queueMicrotask(f) { __queue_job(f); }