shelving
Version:
Toolkit for using data in JavaScript.
74 lines (73 loc) • 2.73 kB
JavaScript
import { getDeferred, getDelay } from "./async.js";
import { call, callMethod } from "./callback.js";
import { STOP } from "./constants.js";
/**
* Is a value an async iterable object?
* - Any object with a `Symbol.iterator` property is iterable.
* - Note: Array and Map instances etc will return true because they implement `Symbol.iterator`
*/
export function isSequence(value) {
return typeof value === "object" && !!value && Symbol.asyncIterator in value;
}
/** Infinite sequence that yields until a `SIGNAL` is received. */
export async function* repeatUntil(source, ...signals) {
const iterator = source[Symbol.asyncIterator]();
while (true) {
const result = await Promise.race([iterator.next(), ...signals]);
if (result === STOP) {
await iterator.return?.(); // Make sure we call `return()` on the iterator because it might do cleanup.
return STOP;
}
if (result.done) {
return result.value;
}
yield result.value;
}
}
/** Infinite sequence that yields every X milliseconds (yields a count of the number of iterations). */
export async function* repeatDelay(ms) {
let count = 1;
while (true) {
await getDelay(ms);
yield count++;
}
}
/** Dispatch items in a sequence to a (possibly async) callback. */
export async function* callSequence(sequence, callback) {
for await (const item of sequence) {
call(callback, item);
yield item;
}
}
/** Pull values from a sequence until the returned function is called. */
export function runSequence(sequence, onNext, onError) {
const { promise, resolve } = getDeferred();
void _runSequence(sequence[Symbol.asyncIterator](), promise, onNext, onError);
return resolve;
}
async function _runSequence(sequence, stopped, onNext, onError) {
try {
const result = await Promise.race([stopped, sequence.next()]);
if (!result) {
// Stop iteration because the stop signal was sent.
// Call `return()` on the iterator so it can perform any clean up.
callMethod(sequence, "return");
return;
}
if (result.done) {
// Stop iteration because iterator is done.
// Don't need to call `return()` on the iterator (assume it stopped itself when it sent `done: true`).
return;
}
// Forward the value to the next callback.
if (onNext)
call(onNext, result.value);
}
catch (thrown) {
// Forward the error to the error callback.
if (onError)
call(onError, thrown);
}
// Continue iteration.
return _runSequence(sequence, stopped, onNext, onError);
}