shelving
Version:
Toolkit for using data in JavaScript.
82 lines (81 loc) • 2.95 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;
/** Cancel the current resolution or rejection. */
cancel() {
this._nextValue = _NOVALUE;
this._nextReason = _NOVALUE;
}
/** Fulfill the current deferred by resolving or rejecting it. */
_fulfill() {
const _deferred = this._deferred;
const _nextReason = this._nextReason;
const _nextValue = this._nextValue;
this._nextReason = _NOVALUE;
this._nextValue = _NOVALUE;
if (_deferred) {
if (_nextReason !== _NOVALUE) {
this._deferred = undefined;
_deferred.reject(_nextReason);
}
else if (_nextValue !== _NOVALUE) {
this._deferred = undefined;
_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;
}
}
}