UNPKG

shelving

Version:

Toolkit for using data in JavaScript.

101 lines (100 loc) 3.64 kB
import { createDeferred } from "../util/async.js"; import { awaitDispose } from "../util/dispose.js"; import { Sequence } from "./Sequence.js"; /** * Deferred sequence of values that can be async iterated and new values can be published. * - Implements `Deferred` so the next result can be set. * - Implements `Promise` so the next result can be awaited. * - Implements `AsyncIterable` so values can be iterated over using `for await...of` * - Call `resolve(value)` to publish the next value, `reject(reason?)` to publish an error, or `done(value?)` to signal completion. */ export class DeferredSequence extends Sequence { /** Lazy deferred that stores iterator values. */ _iteratorDeferred; /** Lazy deferred that stores promise values. */ _promiseDeferred; /** Next iterator result to reject the deferred to (on next microtask). */ _next; /** Get the next promise to be resolved/rejected. */ get promise() { this._promiseDeferred ||= createDeferred(); return this._promiseDeferred.promise; } /** * Resolve the current deferred in the sequence with a value. * - Sends a `{ value: X }` to any iterators. */ resolve(value) { this._next = { value }; queueMicrotask(() => this._fulfill()); } /** * Reject the current deferred in the sequence. */ reject(reason) { this._next = { reason }; queueMicrotask(() => this._fulfill()); } /** * Signal that the sequence is done, causing any active `for await` loops to exit cleanly. * - Sends a `{ done: true, value: R }` to any iterators. */ done(value) { this._next = { done: true, value }; queueMicrotask(() => this._fulfill()); } /** * Cancel the current resolution or rejection. * - Iterators will contain to wait for a next value. */ cancel() { this._next = undefined; } /** Fulfill the current deferred by resolving or rejecting both the iterator deferred and the promise deferred. */ _fulfill() { const iteratorDeferred = this._iteratorDeferred; const promiseDeferred = this._promiseDeferred; const next = this._next; if (next) { this._next = undefined; this._iteratorDeferred = undefined; this._promiseDeferred = undefined; if ("reason" in next) { iteratorDeferred?.reject(next.reason); promiseDeferred?.reject(next.reason); } else { iteratorDeferred?.resolve(next); if (!next.done) promiseDeferred?.resolve(next.value); } } } /** Resolve the current deferred from a sequence of values. */ async *through(sequence) { for await (const item of sequence) { this.resolve(item); yield item; } } // Implement `AsyncIterator` — returns the promise directly since it already resolves to IteratorResult. next(_next) { this._iteratorDeferred ||= createDeferred(); return this._iteratorDeferred.promise; } // Implement `Promise` then(onNext, onError) { return this.promise.then(onNext, onError); } catch(onError) { return this.promise.catch(onError); } finally(onFinally) { return this.promise.finally(onFinally); } // Implement `AsyncIterable` async [Symbol.asyncDispose]() { await awaitDispose(() => this.done(), // Send `done: true` to all consumers of this sequence. super[Symbol.asyncDispose]()); } }