async-sequential-runner
Version:
TypeScript class for running async tasks to completion in sequence.
111 lines (110 loc) • 4.4 kB
JavaScript
;
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
};
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Helper async generator class that runs async task from a queue,
* removing them from the queue when they complete.
* @param queue The queue to get operations from.
*/
function serialAsyncRunner(queue) {
return __asyncGenerator(this, arguments, function* serialAsyncRunner_1() {
while (queue.length > 0) {
const task = queue[0];
yield yield __await(yield __await(task(queue.length > 1).then(v => {
queue.shift();
return v;
})));
}
});
}
/**
* Runs async tasks sequentially.
*/
class AsyncSequentialRunner {
/**
* Runs a task after previous scheduled operations
* have run, returning a promise with the result.
* @param operation A function that will be called to perform
* work, with a boolean indicating if more operations are scheduled.
*/
async run(task) {
const it = this.activeIterator ||
(() => {
const queue = [];
const iter = {
queue,
gen: serialAsyncRunner(queue),
};
this.activeIterator = iter;
return iter;
})();
it.queue.push(task);
return it.gen.next().then(itResult => {
if (it.queue.length === 0) {
this.activeIterator = undefined;
}
if (this.triggers) {
const triggers = this.triggers;
this.triggers = undefined;
triggers.forEach(resFn => {
resFn();
});
}
return itResult.value;
});
}
/**
* Creates a trigger with a promise that will complete after the
* next task has completed. The trigger can be cancelled by
* calling the cancel function. Useful to trigger polling functions
* that can schedule a task when the trigger promise completes.
*/
taskCompleteTrigger() {
if (!this.triggers) {
this.triggers = [];
}
const triggers = this.triggers;
const p = new Promise((resolve, reject) => {
triggers.push(resolve);
});
const res = triggers[triggers.length - 1];
return {
cancel: () => {
if (this.triggers) {
const idx = this.triggers.findIndex(v => {
return v === res;
});
if (idx >= 0) {
if (this.triggers.length === 1) {
this.triggers = undefined;
}
else {
this.triggers.splice(idx, 1);
}
}
}
},
promise: p,
};
}
/**
* Returns whether there are more tasks scheduled or triggers waiting
* for task completion, useful for determining when to clean up
* references to the runner.
*/
hasTasksOrTriggers() {
return !!((this.triggers && this.triggers.length > 0) || (this.activeIterator && this.activeIterator.queue.length));
}
}
exports.AsyncSequentialRunner = AsyncSequentialRunner;