UNPKG

@serenity-js/core

Version:

The core Serenity/JS framework, providing the Screenplay Pattern interfaces, as well as the test reporting and integration infrastructure

154 lines 4.78 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Scheduler = void 0; const errors_1 = require("../../../errors"); const Duration_1 = require("./Duration"); /** * @group Time */ class Scheduler { clock; interactionTimeout; scheduledOperations = []; /** * @param clock * @param interactionTimeout * The maximum amount of time to give to a callback to complete before throwing an error */ constructor(clock, interactionTimeout) { this.clock = clock; this.interactionTimeout = interactionTimeout; } toJSON() { return { clock: this.clock.toJSON(), interactionTimeout: this.interactionTimeout.toJSON(), }; } /** * Schedules a callback function to be invoked after a delay * * @param delay * @param callback */ after(delay, callback) { return this.repeatUntil(callback, { maxInvocations: 1, delayBetweenInvocations: () => delay, timeout: this.interactionTimeout.plus(delay), }); } /** * Returns a `Promise` to be resolved after a `delay` * * @param delay */ waitFor(delay) { return this.repeatUntil(() => void 0, { maxInvocations: 1, delayBetweenInvocations: () => delay, // make sure waitFor doesn't get terminated before it's resolved timeout: this.interactionTimeout.plus(delay), }); } /** * Schedules a callback function to be repeated, according to configured limits. * * @param callback * @param limits */ async repeatUntil(callback, limits = {}) { const { maxInvocations = Number.POSITIVE_INFINITY, delayBetweenInvocations = noDelay, timeout = this.interactionTimeout, exitCondition = noEarlyExit, errorHandler = rethrowErrors, } = limits; const operation = new ScheduledOperation(this.clock, callback, { exitCondition, maxInvocations, delayBetweenInvocations, timeout, errorHandler, }); this.scheduledOperations.push(operation); return operation.start(); } stop() { for (const operation of this.scheduledOperations) { operation.cancel(); } } } exports.Scheduler = Scheduler; class ScheduledOperation { clock; callback; limits; currentInvocation = 0; invocationsLeft = 0; startedAt; lastResult; isCancelled = false; constructor(clock, callback, limits = {}) { this.clock = clock; this.callback = callback; this.limits = limits; } async start() { this.currentInvocation = 0; this.invocationsLeft = this.limits.maxInvocations; this.startedAt = this.clock.now(); return await this.poll(); } async poll() { await this.clock.waitFor(this.limits.delayBetweenInvocations(this.currentInvocation)); if (this.isCancelled) { throw new errors_1.OperationInterruptedError('Scheduler stopped before executing callback'); } const receipt = await this.invoke(); if (receipt.hasCompleted) { return receipt.result; } this.currentInvocation++; this.invocationsLeft--; return await this.poll(); } async invoke() { const timeoutExpired = this.startedAt.plus(this.limits.timeout).isBefore(this.clock.now()); const isLastInvocation = this.invocationsLeft === 1; if (this.invocationsLeft === 0) { return { result: this.lastResult, hasCompleted: true, }; } try { if (timeoutExpired) { throw new errors_1.TimeoutExpiredError(`Timeout of ${this.limits.timeout} has expired`); } this.lastResult = await this.callback({ currentTime: this.clock.now(), i: this.currentInvocation }); return { result: this.lastResult, hasCompleted: this.limits.exitCondition(this.lastResult) || isLastInvocation, }; } catch (error) { this.limits.errorHandler(error, this.lastResult); // if the errorHandler didn't throw, it's a recoverable error return { result: this.lastResult, error, hasCompleted: isLastInvocation, }; } } cancel() { this.isCancelled = true; } } function noDelay() { return Duration_1.Duration.ofMilliseconds(0); } function noEarlyExit() { return false; } function rethrowErrors(error) { throw error; } //# sourceMappingURL=Scheduler.js.map