poll-until-promise
Version:
Try repeatedly for a promise to be resolved
164 lines (163 loc) • 6.37 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.waitFor = exports.PollUntil = void 0;
const abort_1 = require("./abort");
const ERRORS = {
NOT_FUNCTION: 'Your executor is not a function. functions and promises are valid.',
FAILED_TO_WAIT: 'Failed to wait',
};
function promisify(fn) {
return () => __awaiter(this, void 0, void 0, function* () {
const result = yield fn();
return result;
});
}
function validateExecution(executeFn) {
if (typeof executeFn !== 'function') {
throw new Error(ERRORS.NOT_FUNCTION);
}
}
class PollUntil {
constructor({ interval = 100, timeout = 1000, stopOnFailure = false, verbose = false, backoffFactor = 1, backoffMaxInterval, message = '', maxAttempts, } = {}) {
this._interval = interval;
this._timeout = timeout;
this._executedAttempts = 0;
this._stopOnFailure = stopOnFailure;
this._isWaiting = false;
this._isResolved = false;
this._verbose = verbose;
this._userMessage = message;
this.originalStacktraceError = new Error();
this._Console = console;
this._backoffFactor = backoffFactor;
this._backoffMaxInterval = backoffMaxInterval || timeout;
this._maxAttempts = maxAttempts;
this.start = +Date.now();
}
tryEvery(interval) {
this._interval = interval;
return this;
}
stopAfter(timeout) {
this._timeout = timeout;
return this;
}
execute(executeFn) {
this._applyPromiseHandlers();
validateExecution(executeFn);
this._executeFn = promisify(executeFn);
this.start = Date.now();
this._isWaiting = true;
this._log('starting to execute');
this._runFunction();
return this.promise;
}
getPromise() {
return this.promise;
}
isResolved() {
return this._isResolved;
}
isWaiting() {
return this._isWaiting;
}
stopOnFailure(stop) {
this._stopOnFailure = stop;
return this;
}
_applyPromiseHandlers() {
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
_timeFromStart() {
return Date.now() - this.start;
}
_shouldStopTrying() {
return this._timeFromStart() > this._timeout || this._attemptsExhausted();
}
_attemptsExhausted() {
return this._maxAttempts !== undefined && this._executedAttempts >= this._maxAttempts;
}
_executeAgain() {
this._log('executing again');
const currentInterval = this._interval;
const nextInterval = currentInterval * this._backoffFactor;
this._interval = (nextInterval > this._backoffMaxInterval) ? this._backoffMaxInterval : nextInterval;
this._executedAttempts += 1;
setTimeout(this._runFunction.bind(this), currentInterval);
}
_failedToWait() {
const timeFromStartStr = `${this._timeFromStart()}ms`;
let waitErrorText = this._attemptsExhausted()
? `Operation unsuccessful after ${this._executedAttempts} attempts (total of ${timeFromStartStr})`
: `${ERRORS.FAILED_TO_WAIT} after ${timeFromStartStr} (total of ${this._executedAttempts} attempts)`;
if (this._userMessage)
waitErrorText = `${waitErrorText}: ${this._userMessage}`;
if (this._lastError) {
this._lastError.message = `${waitErrorText}\n${this._lastError.message}`;
const originalStack = this.originalStacktraceError.stack;
if (originalStack) {
this._lastError.stack += originalStack.substring(originalStack.indexOf('\n') + 1);
}
}
else {
this._lastError = this.originalStacktraceError;
this._lastError.message = waitErrorText;
}
this._log(this._lastError);
return this._lastError;
}
_runFunction() {
var _a;
if (this._shouldStopTrying()) {
this._isWaiting = false;
(_a = this.reject) === null || _a === void 0 ? void 0 : _a.call(this, this._failedToWait());
return;
}
this._executeFn()
.then((result) => {
var _a;
if (result === false) {
this._log(`then execute again with result: ${result}`);
this._executeAgain();
return;
}
(_a = this.resolve) === null || _a === void 0 ? void 0 : _a.call(this, result);
this._isWaiting = false;
this._isResolved = true;
this._log(`then done waiting with result: ${result}`);
})
.catch((err) => {
var _a, _b;
if (err instanceof abort_1.AbortError) {
this._log(`aborted with err: ${err.cause}`);
return (_a = this.reject) === null || _a === void 0 ? void 0 : _a.call(this, err.cause);
}
if (this._stopOnFailure) {
this._log(`stopped on failure with err: ${err}`);
return (_b = this.reject) === null || _b === void 0 ? void 0 : _b.call(this, err);
}
this._lastError = err;
this._log(`catch with err: ${err}`);
return this._executeAgain();
});
}
_log(message) {
if (this._verbose && this._Console && this._Console.log)
this._Console.log(message);
}
}
exports.PollUntil = PollUntil;
const waitFor = (waitForFunction, options) => new PollUntil(options).execute(waitForFunction);
exports.waitFor = waitFor;