shelving
Version:
Toolkit for using data in JavaScript.
101 lines (100 loc) • 3.64 kB
JavaScript
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]());
}
}