333 lines
8.8 KiB
JavaScript
333 lines
8.8 KiB
JavaScript
goog.provide("promesa.impl.promise");
|
|
goog.provide("promesa.impl.promise.PromiseImpl");
|
|
goog.provide("promesa.impl.promise.CancellationError");
|
|
goog.scope(function() {
|
|
function isCancellationError(v) {
|
|
return v instanceof CancellationError;
|
|
}
|
|
function fmtValue(o) {
|
|
if (isThenable(o)) {
|
|
return `<PROMISE:${goog.getUid(o)}>`;
|
|
} else if (o instanceof Error) {
|
|
return `<EXCEPTION:'${o.message}'>`;
|
|
} else if (o === null || o === undefined) {
|
|
return `${o}`;
|
|
} else if (typeof o === "function") {
|
|
return `<FN:${goog.getUid(o)}>`;
|
|
} else {
|
|
return `${o.toString()}`;
|
|
}
|
|
}
|
|
function isSome(o) {
|
|
return o !== null && o !== undefined;
|
|
}
|
|
function isFunction(o) {
|
|
return typeof o === "function";
|
|
}
|
|
function isThenable(o) {
|
|
if (goog.isObject(o)) {
|
|
const thenFn = o.then;
|
|
return isFunction(thenFn);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
function constantly(v) {
|
|
return () => {
|
|
return v;
|
|
};
|
|
}
|
|
function identity(v) {
|
|
return v;
|
|
}
|
|
function isPromiseImpl(v) {
|
|
return v instanceof PromiseImpl;
|
|
}
|
|
function completeDeferredFn(deferred) {
|
|
return (value, cause) => {
|
|
if (cause) {
|
|
deferred.reject(cause);
|
|
} else {
|
|
deferred.resolve(value);
|
|
}
|
|
};
|
|
}
|
|
function process(p) {
|
|
if (p[STATE] === PENDING) {
|
|
return;
|
|
}
|
|
nextTick(processNextTick, p);
|
|
return p;
|
|
}
|
|
function processNextTick(p) {
|
|
if (p[QUEUE].length === 0) {
|
|
return;
|
|
}
|
|
const state = p[STATE];
|
|
const value = p[VALUE];
|
|
let task;
|
|
let rvalue;
|
|
let rcause;
|
|
for (; p[QUEUE].length;) {
|
|
task = p[QUEUE].shift();
|
|
try {
|
|
if (state === RESOLVED) {
|
|
rvalue = task.resolve(value);
|
|
} else if (state === REJECTED) {
|
|
rvalue = task.reject(value);
|
|
} else {
|
|
rcause = new TypeError("invalid state");
|
|
}
|
|
} catch (e) {
|
|
rcause = e;
|
|
}
|
|
resolveTask(task, rvalue, rcause);
|
|
}
|
|
}
|
|
function resolveTask(task, value, cause) {
|
|
if (task.complete === undefined) {
|
|
return;
|
|
}
|
|
if (cause) {
|
|
task.complete(null, cause);
|
|
} else {
|
|
if (task.type === RESOLVE_TYPE_MAP) {
|
|
task.complete(value, null);
|
|
} else if (task.type === RESOLVE_TYPE_FLATTEN) {
|
|
if (isPromiseImpl(value)) {
|
|
value.handle((v, c) => {
|
|
resolveTask(task, v, c);
|
|
});
|
|
} else if (isThenable(value)) {
|
|
value.then(v => {
|
|
resolveTask(task, v, null);
|
|
}, c => {
|
|
resolveTask(task, null, c);
|
|
});
|
|
} else {
|
|
task.complete(value, null);
|
|
}
|
|
} else if (task.type === RESOLVE_TYPE_BIND) {
|
|
if (isPromiseImpl(value)) {
|
|
value.handle((v, c) => {
|
|
task.complete(v, c);
|
|
});
|
|
} else if (isThenable(value)) {
|
|
value.then(v => {
|
|
task.complete(v, null);
|
|
}, c => {
|
|
task.complete(null, c);
|
|
});
|
|
} else {
|
|
task.complete(null, new TypeError("expected thenable"));
|
|
}
|
|
} else {
|
|
task.complete(null, new TypeError("internal: invalid resolve type"));
|
|
}
|
|
}
|
|
}
|
|
function transition(p, state, value) {
|
|
if (p[STATE] === state || p[STATE] !== PENDING) {
|
|
return;
|
|
}
|
|
p[STATE] = state;
|
|
p[VALUE] = value;
|
|
return processNextTick(p);
|
|
}
|
|
const self = promesa.impl.promise;
|
|
const root = goog.global;
|
|
const PENDING = Symbol("state/pending");
|
|
const RESOLVED = Symbol("state/resolved");
|
|
const REJECTED = Symbol("state/rejected");
|
|
const QUEUE = Symbol("queue");
|
|
const STATE = Symbol("state");
|
|
const VALUE = Symbol("value");
|
|
const RESOLVE_TYPE_FLATTEN = Symbol("resolve-type/flatten");
|
|
const RESOLVE_TYPE_BIND = Symbol("resolve-type/bind");
|
|
const RESOLVE_TYPE_MAP = Symbol("resolve-type/map");
|
|
const defaultResolveMapHandler = v => {
|
|
return v;
|
|
};
|
|
const defaultResolveBindHandler = v => {
|
|
return self.resolved(v);
|
|
};
|
|
const defaultRejectHandler = c => {
|
|
throw c;
|
|
};
|
|
class CancellationError extends Error {
|
|
}
|
|
class PromiseImpl {
|
|
constructor(val) {
|
|
this[QUEUE] = [];
|
|
this[STATE] = PENDING;
|
|
this[VALUE] = undefined;
|
|
if (val !== undefined) {
|
|
transition(this, RESOLVED, val);
|
|
}
|
|
}
|
|
get state() {
|
|
return this[STATE];
|
|
}
|
|
get value() {
|
|
return this[VALUE];
|
|
}
|
|
then(resolve, reject) {
|
|
const deferred = new PromiseImpl();
|
|
this[QUEUE].push({type:RESOLVE_TYPE_FLATTEN, resolve:resolve ?? defaultResolveMapHandler, reject:reject ?? defaultRejectHandler, complete:completeDeferredFn(deferred)});
|
|
process(this);
|
|
return deferred;
|
|
}
|
|
catch(reject) {
|
|
return this.then(null, reject);
|
|
}
|
|
finally(f) {
|
|
this[QUEUE].push({type:RESOLVE_TYPE_FLATTEN, resolve:value => {
|
|
return f();
|
|
}, reject:cause => {
|
|
return f();
|
|
}, complete:(value, cause) => {
|
|
return null;
|
|
}});
|
|
return this;
|
|
}
|
|
fmap(resolve, reject) {
|
|
const deferred = new PromiseImpl();
|
|
this[QUEUE].push({type:RESOLVE_TYPE_MAP, resolve:resolve ?? defaultResolveMapHandler, reject:reject ?? defaultRejectHandler, complete:completeDeferredFn(deferred)});
|
|
process(this);
|
|
return deferred;
|
|
}
|
|
fbind(resolve, reject) {
|
|
const deferred = new PromiseImpl();
|
|
this[QUEUE].push({type:RESOLVE_TYPE_BIND, resolve:resolve ?? defaultResolveBindHandler, reject:reject ?? defaultRejectHandler, complete:completeDeferredFn(deferred)});
|
|
process(this);
|
|
return deferred;
|
|
}
|
|
handle(fn, resolveType) {
|
|
resolveType = resolveType ?? RESOLVE_TYPE_MAP;
|
|
this[QUEUE].push({type:resolveType, resolve:defaultResolveMapHandler, reject:defaultRejectHandler, complete:fn});
|
|
process(this);
|
|
}
|
|
resolve(value) {
|
|
if (this[STATE] === PENDING) {
|
|
transition(this, RESOLVED, value);
|
|
}
|
|
return null;
|
|
}
|
|
reject(cause) {
|
|
if (this[STATE] === PENDING) {
|
|
transition(this, REJECTED, cause);
|
|
}
|
|
return null;
|
|
}
|
|
isPending() {
|
|
const state = this[STATE];
|
|
return state === PENDING;
|
|
}
|
|
isResolved() {
|
|
const state = this[STATE];
|
|
return state === RESOLVED;
|
|
}
|
|
isRejected() {
|
|
const state = this[STATE];
|
|
return state === REJECTED;
|
|
}
|
|
isCancelled() {
|
|
const state = this[STATE];
|
|
const value = this[VALUE];
|
|
return state === REJECTED && isCancellationError(value);
|
|
}
|
|
cancel() {
|
|
this.reject(new CancellationError("promise cancelled"));
|
|
}
|
|
}
|
|
const nextTick = (() => {
|
|
if (typeof root.Promise === "function") {
|
|
const resolved = Promise.resolve(null);
|
|
return function queueMicrotaskWithPromise(f, p) {
|
|
resolved.then(() => {
|
|
return f(p);
|
|
});
|
|
};
|
|
} else if (typeof root.setImmediate === "function") {
|
|
return root.setImmediate;
|
|
} else if (typeof root.setTimeout === "function") {
|
|
return (f, p) => {
|
|
return root.setTimeout(f, 0, p);
|
|
};
|
|
} else {
|
|
return (f, p) => {
|
|
return f.call(this, p);
|
|
};
|
|
}
|
|
})();
|
|
self.PromiseImpl = PromiseImpl;
|
|
self.CancellationError = CancellationError;
|
|
self.isCancellationError = isCancellationError;
|
|
self.deferred = () => {
|
|
return new PromiseImpl();
|
|
};
|
|
const NULL_PROMISE = new PromiseImpl(null);
|
|
self.resolved = function resolved(value) {
|
|
if (value === null) {
|
|
return NULL_PROMISE;
|
|
} else {
|
|
const p = new PromiseImpl();
|
|
p[STATE] = RESOLVED;
|
|
p[VALUE] = value;
|
|
return p;
|
|
}
|
|
};
|
|
self.rejected = function rejected(reason) {
|
|
const p = new PromiseImpl();
|
|
p[STATE] = REJECTED;
|
|
p[VALUE] = reason;
|
|
return p;
|
|
};
|
|
self.all = function all(promises) {
|
|
return promises.reduce((acc, p) => {
|
|
return acc.then(results => {
|
|
return self.coerce(p).fmap(v => {
|
|
results.push(v);
|
|
return results;
|
|
});
|
|
});
|
|
}, self.resolved([]));
|
|
};
|
|
self.coerce = function coerce(promise) {
|
|
if (promise instanceof PromiseImpl) {
|
|
return promise;
|
|
} else if (isThenable(promise)) {
|
|
const deferred = self.deferred();
|
|
promise.then(v => {
|
|
deferred.resolve(v);
|
|
}, c => {
|
|
deferred.reject(c);
|
|
});
|
|
return deferred;
|
|
} else if (promise instanceof Error) {
|
|
return self.rejected(promise);
|
|
} else {
|
|
return self.resolved(promise);
|
|
}
|
|
};
|
|
self.race = function race(promises) {
|
|
const deferred = self.deferred();
|
|
promises.forEach(p => {
|
|
self.coerce(p).handle((v, c) => {
|
|
if (c) {
|
|
deferred.reject(c);
|
|
} else {
|
|
deferred.resolve(v);
|
|
}
|
|
});
|
|
});
|
|
return deferred;
|
|
};
|
|
self.nextTick = nextTick;
|
|
self.PENDING = PENDING;
|
|
self.RESOLVED = RESOLVED;
|
|
self.REJECTED = REJECTED;
|
|
});
|
|
|
|
//# sourceMappingURL=promesa.impl.promise.js.map
|