/** * @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" */ import * as queue from './queue.js' import * as object from './object.js' /* c8 ignore start */ /** * @type {queue.Queuevoid>>} */ 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} Pledge */ /** * @template {any} Val * @template {any} [CancelReason=Error] */ export class PledgeInstance { constructor () { /** * @type {Val | CancelReason | null} */ this._v = null this.isResolved = false /** * @type {Array | null} */ this._whenResolved = [] /** * @type {Array | 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} f * @return {PledgeInstance} */ map (f) { /** * @type {PledgeInstance} */ 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} */ promise () { return new Promise((resolve, reject) => { this.whenResolved(resolve) this.whenCanceled(reject) }) } } /** * @template T * @return {PledgeInstance} */ export const create = () => new PledgeInstance() /** * @typedef {Array> | Object>} PledgeMap */ /** * @template {Pledge | PledgeMap} P * @typedef {P extends PledgeMap ? { [K in keyof P]: P[K] extends Pledge ? V : P[K]} : (P extends Pledge ? V : never)} Resolved

*/ /** * @todo Create a "resolveHelper" that will simplify creating indxeddbv2 functions. Double arguments * are not necessary. * * @template V * @template {Array>} DEPS * @param {(p: PledgeInstance, ...deps: Resolved) => void} init * @param {DEPS} deps * @return {PledgeInstance} */ export const createWithDependencies = (init, ...deps) => { /** * @type {PledgeInstance} */ const p = new PledgeInstance() // @ts-ignore @todo remove this all(deps).whenResolved(ds => init(p, ...ds)) return p } /** * @template R * @param {Pledge} p * @param {function(R):void} f */ export const whenResolved = (p, f) => { if (p instanceof PledgeInstance) { return p.whenResolved(f) } return f(p) } /** * @template {Pledge} P * @param {P} p * @param {P extends PledgeInstance ? function(CancelReason):void : function(any):void} f */ export const whenCanceled = (p, f) => { if (p instanceof PledgeInstance) { p.whenCanceled(f) } } /** * @template P * @template Q * @param {Pledge

} p * @param {(r: P) => Q} f * @return {Pledge} */ export const map = (p, f) => { if (p instanceof PledgeInstance) { return p.map(f) } return f(p) } /** * @template {PledgeMap} PS * @param {PS} ps * @return {PledgeInstance>} */ export 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 | PledgeInstance, Result, any>} f * @return {PledgeInstance} */ export 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} */ export const wait = timeout => { const p = create() setTimeout(p.resolve.bind(p), timeout) return p } /* c8 ignore end */