lib0
Version:
> Monorepo of isomorphic utility functions
308 lines (279 loc) • 6.53 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var queue = require('./queue.cjs');
var object = require('./object-315a738b.cjs');
/**
* @experimental Use of this module is not encouraged!
* This is just an experiment.
* @todo remove `c8 ignore` line once this is moved to "non-experimental"
*/
/* c8 ignore start */
/**
* @type {queue.Queue<queue.QueueValue<()=>void>>}
*/
const ctxFs = queue.create();
/**
* @param {() => void} f
*/
const runInGlobalContext = f => {
const isEmpty = queue.isEmpty(ctxFs);
queue.enqueue(ctxFs, new queue.QueueValue(f));
if (isEmpty) {
while (!queue.isEmpty(ctxFs)) {
/** @type {queue.QueueValue<()=>{}>} */ (ctxFs.start).v();
queue.dequeue(ctxFs);
}
}
};
/**
* @template V
* @typedef {V | PledgeInstance<V>} Pledge
*/
/**
* @template {any} Val
* @template {any} [CancelReason=Error]
*/
class PledgeInstance {
constructor () {
/**
* @type {Val | CancelReason | null}
*/
this._v = null;
this.isResolved = false;
/**
* @type {Array<function(Val):void> | null}
*/
this._whenResolved = [];
/**
* @type {Array<function(CancelReason):void> | null}
*/
this._whenCanceled = [];
}
get isDone () {
return this._whenResolved === null
}
get isCanceled () {
return !this.isResolved && this._whenResolved === null
}
/**
* @param {Val} v
*/
resolve (v) {
const whenResolved = this._whenResolved;
if (whenResolved === null) return
this._v = v;
this.isResolved = true;
this._whenResolved = null;
this._whenCanceled = null;
for (let i = 0; i < whenResolved.length; i++) {
whenResolved[i](v);
}
}
/**
* @param {CancelReason} reason
*/
cancel (reason) {
const whenCanceled = this._whenCanceled;
if (whenCanceled === null) return
this._v = reason;
this._whenResolved = null;
this._whenCanceled = null;
for (let i = 0; i < whenCanceled.length; i++) {
whenCanceled[i](reason);
}
}
/**
* @template R
* @param {function(Val):Pledge<R>} f
* @return {PledgeInstance<R>}
*/
map (f) {
/**
* @type {PledgeInstance<R>}
*/
const p = new PledgeInstance();
this.whenResolved(v => {
const result = f(v);
if (result instanceof PledgeInstance) {
if (result._whenResolved === null) {
result.resolve(/** @type {R} */ (result._v));
} else {
result._whenResolved.push(p.resolve.bind(p));
}
} else {
p.resolve(result);
}
});
return p
}
/**
* @param {function(Val):void} f
*/
whenResolved (f) {
if (this.isResolved) {
f(/** @type {Val} */ (this._v));
} else {
this._whenResolved?.push(f);
}
}
/**
* @param {(reason: CancelReason) => void} f
*/
whenCanceled (f) {
if (this.isCanceled) {
f(/** @type {CancelReason} */ (this._v));
} else {
this._whenCanceled?.push(f);
}
}
/**
* @return {Promise<Val>}
*/
promise () {
return new Promise((resolve, reject) => {
this.whenResolved(resolve);
this.whenCanceled(reject);
})
}
}
/**
* @template T
* @return {PledgeInstance<T>}
*/
const create = () => new PledgeInstance();
/**
* @typedef {Array<Pledge<unknown>> | Object<string,Pledge<unknown>>} PledgeMap
*/
/**
* @template {Pledge<unknown> | PledgeMap} P
* @typedef {P extends PledgeMap ? { [K in keyof P]: P[K] extends Pledge<infer V> ? V : P[K]} : (P extends Pledge<infer V> ? V : never)} Resolved<P>
*/
/**
* @todo Create a "resolveHelper" that will simplify creating indxeddbv2 functions. Double arguments
* are not necessary.
*
* @template V
* @template {Array<Pledge<unknown>>} DEPS
* @param {(p: PledgeInstance<V>, ...deps: Resolved<DEPS>) => void} init
* @param {DEPS} deps
* @return {PledgeInstance<V>}
*/
const createWithDependencies = (init, ...deps) => {
/**
* @type {PledgeInstance<V>}
*/
const p = new PledgeInstance();
// @ts-ignore @todo remove this
all(deps).whenResolved(ds => init(p, ...ds));
return p
};
/**
* @template R
* @param {Pledge<R>} p
* @param {function(R):void} f
*/
const whenResolved = (p, f) => {
if (p instanceof PledgeInstance) {
return p.whenResolved(f)
}
return f(p)
};
/**
* @template {Pledge<unknown>} P
* @param {P} p
* @param {P extends PledgeInstance<unknown, infer CancelReason> ? function(CancelReason):void : function(any):void} f
*/
const whenCanceled = (p, f) => {
if (p instanceof PledgeInstance) {
p.whenCanceled(f);
}
};
/**
* @template P
* @template Q
* @param {Pledge<P>} p
* @param {(r: P) => Q} f
* @return {Pledge<Q>}
*/
const map = (p, f) => {
if (p instanceof PledgeInstance) {
return p.map(f)
}
return f(p)
};
/**
* @template {PledgeMap} PS
* @param {PS} ps
* @return {PledgeInstance<Resolved<PS>>}
*/
const all = ps => {
/**
* @type {any}
*/
const pall = create();
/**
* @type {any}
*/
const result = ps instanceof Array ? new Array(ps.length) : {};
let waitingPs = ps instanceof Array ? ps.length : object.size(ps);
for (const key in ps) {
const p = ps[key];
whenResolved(p, r => {
result[key] = r;
if (--waitingPs === 0) {
// @ts-ignore
pall.resolve(result);
}
});
}
return pall
};
/**
* @template Result
* @template {any} YieldResults
* @param {() => Generator<Pledge<YieldResults> | PledgeInstance<YieldResults,any>, Result, any>} f
* @return {PledgeInstance<Result>}
*/
const coroutine = f => {
const p = create();
const gen = f();
/**
* @param {any} [yv]
*/
const handleGen = (yv) => {
const res = gen.next(yv);
if (res.done) {
p.resolve(res.value);
return
}
// @ts-ignore
whenCanceled(res.value, (reason) => {
gen.throw(reason);
});
runInGlobalContext(() =>
whenResolved(res.value, handleGen)
);
};
handleGen();
return p
};
/**
* @param {number} timeout
* @return {PledgeInstance<undefined>}
*/
const wait = timeout => {
const p = create();
setTimeout(p.resolve.bind(p), timeout);
return p
};
/* c8 ignore end */
exports.PledgeInstance = PledgeInstance;
exports.all = all;
exports.coroutine = coroutine;
exports.create = create;
exports.createWithDependencies = createWithDependencies;
exports.map = map;
exports.wait = wait;
exports.whenCanceled = whenCanceled;
exports.whenResolved = whenResolved;
//# sourceMappingURL=pledge.cjs.map