shelving
Version:
Toolkit for using data in JavaScript.
72 lines (71 loc) • 2.65 kB
JavaScript
import { getDeferred } from "../util/async.js";
import { AbstractSequence } from "./AbstractSequence.js";
/** Used when the deferred sequence has no value or reason queued. */
const _NOVALUE = Symbol("shelving/DeferredSequence.NOVALUE");
/**
* Deferred sequence of values that can be async iterated and new values can be published.
* - Implements `AsyncIterable` so values can be iterated over using `for await...of`
* - Implements `Promise` so the next value can be awaited.
* - Implements `Deferred` so next values can be resolved or rejected.
*/
export class DeferredSequence extends AbstractSequence {
/**
* Next deferred to be rejected/resolved, or `undefined` if we haven't requested one yet..
* - Only create the deferred on demand, because we don't want to reject a deferred that isn't used to or it would throw an unhandled promise error.
*/
_deferred;
/** Get the next promise to be deferred/rejected. */
get promise() {
this._deferred ||= getDeferred();
return this._deferred.promise;
}
/** Resolve the current deferred in the sequence. */
resolve(value) {
this._nextValue = value;
this._nextReason = _NOVALUE;
queueMicrotask(() => this._fulfill());
}
_nextValue = _NOVALUE;
/** Reject the current deferred in the sequence. */
reject(reason) {
this._nextValue = _NOVALUE;
this._nextReason = reason;
queueMicrotask(() => this._fulfill());
}
_nextReason = _NOVALUE;
/** Fulfill the current deferred by resolving or rejecting it. */
_fulfill() {
const { _deferred, _nextReason, _nextValue } = this;
this._deferred = undefined;
this._nextReason = _NOVALUE;
this._nextValue = _NOVALUE;
if (_deferred) {
if (_nextReason !== _NOVALUE)
_deferred.reject(_nextReason);
else if (_nextValue !== _NOVALUE)
_deferred.resolve(_nextValue);
}
}
// Implement `AsyncIterator`
async next() {
return { value: await this.promise };
}
// Implement `Promise`
// biome-ignore lint/suspicious/noThenProperty: This is intentional.
then(onNext, onError) {
return this.promise.then(onNext, onError);
}
catch(onError) {
return this.promise.catch(onError);
}
finally(onFinally) {
return this.promise.finally(onFinally);
}
/** Resolve the current deferred from a sequence of values. */
async *through(sequence) {
for await (const item of sequence) {
this.resolve(item);
yield item;
}
}
}