Locked in type model
This commit is contained in:
+74
-40
@@ -3,6 +3,17 @@
|
||||
// `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;
|
||||
@@ -11,24 +22,49 @@ 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 = [];
|
||||
const p = { __proto__: this.__proto__, state: __PENDING, value: undefined, reason: undefined, resolved: false, reactions: [] };
|
||||
|
||||
if (executor !== undefined) {
|
||||
executor((value) => __resolve(this, value), (reason) => __reject(this, reason));
|
||||
executor((value) => __resolve(p, value), (reason) => __reject(p, reason));
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
then(onFulfilled, onRejected) {
|
||||
const derived = new Promise();
|
||||
const reaction = { onFulfilled: onFulfilled, onRejected: onRejected, derived: derived };
|
||||
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 {
|
||||
__queue_reaction(reaction, this.state, this.value);
|
||||
reaction.reject(this.reason);
|
||||
}
|
||||
|
||||
return derived;
|
||||
@@ -53,19 +89,19 @@ class Promise {
|
||||
|
||||
static resolve(value) {
|
||||
if (__is_promise(value)) return value;
|
||||
const p = new Promise();
|
||||
const p = { __proto__: this.prototype, state: __PENDING, value: undefined, reason: undefined, resolved: false, reactions: [] };
|
||||
__resolve(p, value);
|
||||
return p;
|
||||
}
|
||||
|
||||
static reject(reason) {
|
||||
const p = new Promise();
|
||||
const p = { __proto__: this.prototype, state: __PENDING, value: undefined, reason: undefined, resolved: false, reactions: [] };
|
||||
__reject(p, reason);
|
||||
return p;
|
||||
}
|
||||
|
||||
static all(promises) {
|
||||
const p = new Promise();
|
||||
const p = { __proto__: this.prototype, state: __PENDING, value: undefined, reason: undefined, resolved: false, reactions: [] };
|
||||
const values = [];
|
||||
let left = promises.length;
|
||||
|
||||
@@ -97,6 +133,13 @@ function __resolve(p, value) {
|
||||
__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) {
|
||||
@@ -104,48 +147,39 @@ function __fulfil_or_adopt(p, value) {
|
||||
const then = value.then;
|
||||
|
||||
if (typeof then === "function") {
|
||||
__queue_job(() => __adopt(p, value));
|
||||
__queue_adopt(p, value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
__settle(p, __FULFILLED, value);
|
||||
__fulfil(p, value);
|
||||
}
|
||||
|
||||
function __reject(p, reason) {
|
||||
if (p.resolved) return;
|
||||
p.resolved = true;
|
||||
__settle(p, __REJECTED, reason);
|
||||
// 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));
|
||||
}
|
||||
|
||||
function __settle(p, state, value) {
|
||||
p.state = state;
|
||||
// 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;
|
||||
p.reactions = [];
|
||||
|
||||
for (let i = 0; i < reactions.length; i++) {
|
||||
__queue_reaction(reactions[i], state, value);
|
||||
}
|
||||
for (let i = 0; i < reactions.length; i++) reactions[i].fulfil(value);
|
||||
reactions.length = 0;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
function __settle_rejected(p, reason) {
|
||||
p.state = __REJECTED;
|
||||
p.reason = reason;
|
||||
|
||||
// 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);
|
||||
}
|
||||
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`
|
||||
@@ -155,7 +189,7 @@ function __adopt(p, thenable) {
|
||||
if (p.state === __PENDING) __fulfil_or_adopt(p, value);
|
||||
},
|
||||
(reason) => {
|
||||
if (p.state === __PENDING) __settle(p, __REJECTED, reason);
|
||||
if (p.state === __PENDING) __settle_rejected(p, reason);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user