244 lines
5.2 KiB
JavaScript
244 lines
5.2 KiB
JavaScript
import {
|
|
objectOrFunction,
|
|
isFunction
|
|
} from './utils';
|
|
|
|
import {
|
|
asap
|
|
} from './asap';
|
|
|
|
import originalThen from './then';
|
|
import originalResolve from './promise/resolve';
|
|
|
|
export const PROMISE_ID = Math.random().toString(36).substring(2);
|
|
|
|
function noop() {}
|
|
|
|
const PENDING = void 0;
|
|
const FULFILLED = 1;
|
|
const REJECTED = 2;
|
|
|
|
function selfFulfillment() {
|
|
return new TypeError("You cannot resolve a promise with itself");
|
|
}
|
|
|
|
function cannotReturnOwn() {
|
|
return new TypeError('A promises callback cannot return that same promise.');
|
|
}
|
|
|
|
function tryThen(then, value, fulfillmentHandler, rejectionHandler) {
|
|
try {
|
|
then.call(value, fulfillmentHandler, rejectionHandler);
|
|
} catch(e) {
|
|
return e;
|
|
}
|
|
}
|
|
|
|
function handleForeignThenable(promise, thenable, then) {
|
|
asap(promise => {
|
|
let sealed = false;
|
|
let error = tryThen(then, thenable, value => {
|
|
if (sealed) { return; }
|
|
sealed = true;
|
|
if (thenable !== value) {
|
|
resolve(promise, value);
|
|
} else {
|
|
fulfill(promise, value);
|
|
}
|
|
}, reason => {
|
|
if (sealed) { return; }
|
|
sealed = true;
|
|
|
|
reject(promise, reason);
|
|
}, 'Settle: ' + (promise._label || ' unknown promise'));
|
|
|
|
if (!sealed && error) {
|
|
sealed = true;
|
|
reject(promise, error);
|
|
}
|
|
}, promise);
|
|
}
|
|
|
|
function handleOwnThenable(promise, thenable) {
|
|
if (thenable._state === FULFILLED) {
|
|
fulfill(promise, thenable._result);
|
|
} else if (thenable._state === REJECTED) {
|
|
reject(promise, thenable._result);
|
|
} else {
|
|
subscribe(thenable, undefined, value => resolve(promise, value),
|
|
reason => reject(promise, reason))
|
|
}
|
|
}
|
|
|
|
function handleMaybeThenable(promise, maybeThenable, then) {
|
|
if (maybeThenable.constructor === promise.constructor &&
|
|
then === originalThen &&
|
|
maybeThenable.constructor.resolve === originalResolve) {
|
|
handleOwnThenable(promise, maybeThenable);
|
|
} else {
|
|
if (then === undefined) {
|
|
fulfill(promise, maybeThenable);
|
|
} else if (isFunction(then)) {
|
|
handleForeignThenable(promise, maybeThenable, then);
|
|
} else {
|
|
fulfill(promise, maybeThenable);
|
|
}
|
|
}
|
|
}
|
|
|
|
function resolve(promise, value) {
|
|
if (promise === value) {
|
|
reject(promise, selfFulfillment());
|
|
} else if (objectOrFunction(value)) {
|
|
let then;
|
|
try {
|
|
then = value.then;
|
|
} catch (error) {
|
|
reject(promise, error);
|
|
return;
|
|
}
|
|
handleMaybeThenable(promise, value, then);
|
|
} else {
|
|
fulfill(promise, value);
|
|
}
|
|
}
|
|
|
|
function publishRejection(promise) {
|
|
if (promise._onerror) {
|
|
promise._onerror(promise._result);
|
|
}
|
|
|
|
publish(promise);
|
|
}
|
|
|
|
function fulfill(promise, value) {
|
|
if (promise._state !== PENDING) { return; }
|
|
|
|
promise._result = value;
|
|
promise._state = FULFILLED;
|
|
|
|
if (promise._subscribers.length !== 0) {
|
|
asap(publish, promise);
|
|
}
|
|
}
|
|
|
|
function reject(promise, reason) {
|
|
if (promise._state !== PENDING) { return; }
|
|
promise._state = REJECTED;
|
|
promise._result = reason;
|
|
|
|
asap(publishRejection, promise);
|
|
}
|
|
|
|
function subscribe(parent, child, onFulfillment, onRejection) {
|
|
let { _subscribers } = parent;
|
|
let { length } = _subscribers;
|
|
|
|
parent._onerror = null;
|
|
|
|
_subscribers[length] = child;
|
|
_subscribers[length + FULFILLED] = onFulfillment;
|
|
_subscribers[length + REJECTED] = onRejection;
|
|
|
|
if (length === 0 && parent._state) {
|
|
asap(publish, parent);
|
|
}
|
|
}
|
|
|
|
function publish(promise) {
|
|
let subscribers = promise._subscribers;
|
|
let settled = promise._state;
|
|
|
|
if (subscribers.length === 0) { return; }
|
|
|
|
let child, callback, detail = promise._result;
|
|
|
|
for (let i = 0; i < subscribers.length; i += 3) {
|
|
child = subscribers[i];
|
|
callback = subscribers[i + settled];
|
|
|
|
if (child) {
|
|
invokeCallback(settled, child, callback, detail);
|
|
} else {
|
|
callback(detail);
|
|
}
|
|
}
|
|
|
|
promise._subscribers.length = 0;
|
|
}
|
|
|
|
function invokeCallback(settled, promise, callback, detail) {
|
|
let hasCallback = isFunction(callback),
|
|
value, error, succeeded = true;
|
|
|
|
if (hasCallback) {
|
|
try {
|
|
value = callback(detail);
|
|
} catch (e) {
|
|
succeeded = false;
|
|
error = e;
|
|
}
|
|
|
|
if (promise === value) {
|
|
reject(promise, cannotReturnOwn());
|
|
return;
|
|
}
|
|
} else {
|
|
value = detail;
|
|
}
|
|
|
|
if (promise._state !== PENDING) {
|
|
// noop
|
|
} else if (hasCallback && succeeded) {
|
|
resolve(promise, value);
|
|
} else if (succeeded === false) {
|
|
reject(promise, error);
|
|
} else if (settled === FULFILLED) {
|
|
fulfill(promise, value);
|
|
} else if (settled === REJECTED) {
|
|
reject(promise, value);
|
|
}
|
|
}
|
|
|
|
function initializePromise(promise, resolver) {
|
|
try {
|
|
resolver(function resolvePromise(value){
|
|
resolve(promise, value);
|
|
}, function rejectPromise(reason) {
|
|
reject(promise, reason);
|
|
});
|
|
} catch(e) {
|
|
reject(promise, e);
|
|
}
|
|
}
|
|
|
|
let id = 0;
|
|
function nextId() {
|
|
return id++;
|
|
}
|
|
|
|
function makePromise(promise) {
|
|
promise[PROMISE_ID] = id++;
|
|
promise._state = undefined;
|
|
promise._result = undefined;
|
|
promise._subscribers = [];
|
|
}
|
|
|
|
export {
|
|
nextId,
|
|
makePromise,
|
|
noop,
|
|
resolve,
|
|
reject,
|
|
fulfill,
|
|
subscribe,
|
|
publish,
|
|
publishRejection,
|
|
initializePromise,
|
|
invokeCallback,
|
|
FULFILLED,
|
|
REJECTED,
|
|
PENDING,
|
|
handleMaybeThenable
|
|
};
|