166 lines
4.4 KiB
JavaScript
166 lines
4.4 KiB
JavaScript
// 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);
|
|
}
|