poll-until-promise
Advanced tools
Comparing version
@@ -1,2 +0,4 @@ | ||
import { IWaitForOptions, PollUntil, waitFor } from '../src'; | ||
import { | ||
AbortError, IWaitForOptions, PollUntil, waitFor, | ||
} from '../src'; | ||
@@ -181,20 +183,66 @@ describe('Unit: Wait Until Factory', () => { | ||
it('should fail wait after timeout', (done) => { | ||
it('should fail wait after timeout when stopOnFailure is false', async () => { | ||
const pollUntil = new PollUntil(); | ||
shouldHaltPromiseResolve = true; | ||
shouldRejectAfterHalt = true; | ||
const errorContent = 'error abcdefg'; | ||
const specificFailedError = new Error(errorContent); | ||
pollUntil | ||
.tryEvery(1) | ||
.stopAfter(3000) | ||
.stopOnFailure(false) | ||
.execute(() => Promise.reject(specificFailedError)) | ||
.catch((error) => { | ||
expect(error.message).toContain('Failed to wait'); | ||
expect(error.message).toContain(errorContent); | ||
done(); | ||
}); | ||
const mockPromise = jest.fn(() => Promise.reject(specificFailedError)); | ||
expect.assertions(3); | ||
try { | ||
await pollUntil | ||
.tryEvery(1) | ||
.stopAfter(50) | ||
.stopOnFailure(false) | ||
.execute(mockPromise); | ||
} catch (err) { | ||
const error = err as Error; | ||
expect(error.message).toContain('Failed to wait'); | ||
expect(error.message).toContain(errorContent); | ||
expect(mockPromise.mock.calls.length).toBeGreaterThan(1); | ||
} | ||
}); | ||
it('should fail immediately for AbortError when stopOnFailure is false -- passing Error arg', async () => { | ||
const pollUntil = new PollUntil(); | ||
const errorContent = 'error abcdefg'; | ||
const specificFailedError = new Error(errorContent); | ||
const abortError = new AbortError(specificFailedError); | ||
const mockPromise = jest.fn(() => Promise.reject(abortError)); | ||
expect.assertions(3); | ||
try { | ||
await pollUntil | ||
.tryEvery(1) | ||
.stopAfter(50) | ||
.stopOnFailure(false) | ||
.execute(mockPromise); | ||
} catch (err) { | ||
const error = err as Error; | ||
expect(error.message).not.toContain('Failed to wait'); | ||
expect(error.message).toContain(errorContent); | ||
expect(mockPromise.mock.calls.length).toBe(1); | ||
} | ||
}); | ||
it('should fail immediately for AbortError when stopOnFailure is false -- passing string arg', async () => { | ||
const pollUntil = new PollUntil(); | ||
const errorContent = 'error abcdefg'; | ||
const abortError = new AbortError(errorContent); | ||
const mockPromise = jest.fn(() => Promise.reject(abortError)); | ||
expect.assertions(3); | ||
try { | ||
await pollUntil | ||
.tryEvery(1) | ||
.stopAfter(50) | ||
.stopOnFailure(false) | ||
.execute(mockPromise); | ||
} catch (err) { | ||
const error = err as Error; | ||
expect(error.message).not.toContain('Failed to wait'); | ||
expect(error.message).toContain(errorContent); | ||
expect(mockPromise.mock.calls.length).toBe(1); | ||
} | ||
}); | ||
it('should execute a second waiting when waiting is done (exceeded timeout) but not resolved', (done) => { | ||
@@ -283,3 +331,3 @@ const pollUntil = new PollUntil(); | ||
} catch (e: Error | any) { | ||
expect(e.message).toMatch(/Failed to wait after \d+ms: waiting for something\nFailed to wait after \d+ms: waiting for another thing/); | ||
expect(e.message).toMatch(/Failed to wait after \d+ms \(total of \d+ attempts\): waiting for something\nFailed to wait after \d+ms \(total of \d+ attempts\): waiting for another thing/); | ||
expect(e.stack).toMatch(/alon/); | ||
@@ -299,3 +347,3 @@ } | ||
} | ||
expect(error?.message).toMatch(/^Failed to wait after \d+ms: waiting for something\nsome error message$/); | ||
expect(error?.message).toMatch(/^Failed to wait after \d+ms \(total of \d+ attempts\): waiting for something\nsome error message$/); | ||
}); | ||
@@ -314,3 +362,3 @@ | ||
} | ||
expect(error?.message).toMatch(/^Failed to wait after \d+ms: waiting for something$/); | ||
expect(error?.message).toMatch(/^Failed to wait after \d+ms \(total of \d+ attempts\): waiting for something$/); | ||
expect(error?.stack).toMatch(/customFunction/); | ||
@@ -396,2 +444,23 @@ }); | ||
}); | ||
it('should fail if max attempts exceeded', async () => { | ||
const pollUntil = new PollUntil({ maxAttempts: 3, interval: 1 }); | ||
const error = new Error('whoops'); | ||
const mockPromise = jest.fn().mockRejectedValue(error); | ||
await expect(pollUntil.execute(mockPromise)).rejects.toThrow(/Operation unsuccessful after 3 attempts \(total of \d+ms\)\nwhoops/); | ||
}); | ||
it('should not fail if max attempts not exceeded', async () => { | ||
const pollUntil = new PollUntil({ maxAttempts: 3, interval: 1 }); | ||
const error = new Error('whoops'); | ||
const mockPromise = jest.fn() | ||
.mockRejectedValueOnce(error) | ||
.mockRejectedValueOnce(error) | ||
.mockResolvedValue({ fake: 'result' }); | ||
expect(await pollUntil.execute(mockPromise)).toEqual({ fake: 'result' }); | ||
}); | ||
}); |
@@ -17,2 +17,3 @@ module.exports = { | ||
"@typescript-eslint/lines-between-class-members" : 0, | ||
"import/prefer-default-export": 0, | ||
"no-underscore-dangle": 0, | ||
@@ -19,0 +20,0 @@ "max-len": 0 |
export { IWaitForOptions, PollUntil, waitFor, IExecuteFunction, } from './poll-until-promise'; | ||
export { AbortError } from './abort'; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.waitFor = exports.PollUntil = void 0; | ||
exports.AbortError = exports.waitFor = exports.PollUntil = void 0; | ||
var poll_until_promise_1 = require("./poll-until-promise"); | ||
Object.defineProperty(exports, "PollUntil", { enumerable: true, get: function () { return poll_until_promise_1.PollUntil; } }); | ||
Object.defineProperty(exports, "waitFor", { enumerable: true, get: function () { return poll_until_promise_1.waitFor; } }); | ||
var abort_1 = require("./abort"); | ||
Object.defineProperty(exports, "AbortError", { enumerable: true, get: function () { return abort_1.AbortError; } }); |
@@ -1,2 +0,2 @@ | ||
export declare type IExecuteFunction = any; | ||
export type IExecuteFunction = any; | ||
export interface IWaitForOptions { | ||
@@ -10,2 +10,3 @@ timeout?: number; | ||
verbose?: boolean; | ||
maxAttempts?: number; | ||
} | ||
@@ -15,2 +16,3 @@ export declare class PollUntil { | ||
_timeout: number; | ||
_executedAttempts: number; | ||
private _stopOnFailure; | ||
@@ -23,2 +25,3 @@ private readonly _backoffFactor; | ||
private readonly _verbose; | ||
private readonly _maxAttempts; | ||
private _isWaiting; | ||
@@ -32,3 +35,3 @@ private _isResolved; | ||
private _lastError; | ||
constructor({ interval, timeout, stopOnFailure, verbose, backoffFactor, backoffMaxInterval, message, }?: IWaitForOptions); | ||
constructor({ interval, timeout, stopOnFailure, verbose, backoffFactor, backoffMaxInterval, message, maxAttempts, }?: IWaitForOptions); | ||
tryEvery(interval: number): PollUntil; | ||
@@ -44,2 +47,3 @@ stopAfter(timeout: number): PollUntil; | ||
_shouldStopTrying(): boolean; | ||
_attemptsExhausted(): boolean; | ||
_executeAgain(): void; | ||
@@ -50,2 +54,2 @@ _failedToWait(): Error; | ||
} | ||
export declare const waitFor: (waitForFunction: IExecuteFunction, options?: IWaitForOptions | undefined) => Promise<any>; | ||
export declare const waitFor: (waitForFunction: IExecuteFunction, options?: IWaitForOptions) => Promise<any>; |
@@ -13,2 +13,3 @@ "use strict"; | ||
exports.waitFor = exports.PollUntil = void 0; | ||
const abort_1 = require("./abort"); | ||
const ERRORS = { | ||
@@ -30,5 +31,6 @@ NOT_FUNCTION: 'Your executor is not a function. functions and promises are valid.', | ||
class PollUntil { | ||
constructor({ interval = 100, timeout = 1000, stopOnFailure = false, verbose = false, backoffFactor = 1, backoffMaxInterval, message = '', } = {}) { | ||
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; | ||
@@ -43,2 +45,3 @@ this._isWaiting = false; | ||
this._backoffMaxInterval = backoffMaxInterval || timeout; | ||
this._maxAttempts = maxAttempts; | ||
this.start = +Date.now(); | ||
@@ -87,4 +90,7 @@ } | ||
_shouldStopTrying() { | ||
return this._timeFromStart() > this._timeout; | ||
return this._timeFromStart() > this._timeout || this._attemptsExhausted(); | ||
} | ||
_attemptsExhausted() { | ||
return this._maxAttempts !== undefined && this._executedAttempts >= this._maxAttempts; | ||
} | ||
_executeAgain() { | ||
@@ -95,6 +101,10 @@ this._log('executing again'); | ||
this._interval = (nextInterval > this._backoffMaxInterval) ? this._backoffMaxInterval : nextInterval; | ||
this._executedAttempts += 1; | ||
setTimeout(this._runFunction.bind(this), currentInterval); | ||
} | ||
_failedToWait() { | ||
let waitErrorText = `${ERRORS.FAILED_TO_WAIT} after ${this._timeFromStart()}ms`; | ||
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) | ||
@@ -137,6 +147,10 @@ waitErrorText = `${waitErrorText}: ${this._userMessage}`; | ||
.catch((err) => { | ||
var _a; | ||
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 (_a = this.reject) === null || _a === void 0 ? void 0 : _a.call(this, err); | ||
return (_b = this.reject) === null || _b === void 0 ? void 0 : _b.call(this, err); | ||
} | ||
@@ -143,0 +157,0 @@ this._lastError = err; |
{ | ||
"name": "poll-until-promise", | ||
"version": "4.1.0", | ||
"version": "4.2.0", | ||
"description": "Try repeatedly for a promise to be resolved", | ||
@@ -22,6 +22,5 @@ "main": "lib/index.js", | ||
"test": "jest", | ||
"test:coverage": "jest --coverage && cat ./coverage/lcov.info | coveralls", | ||
"prepare": "npm run build", | ||
"prepublishOnly": "npm test && npm run lint", | ||
"release": "npm run lint && npm run test:coverage && npm run build", | ||
"release": "npm run lint && npm run && npm run build", | ||
"preversion": "npm run lint", | ||
@@ -56,3 +55,2 @@ "version": "npm run lint && git add -A src", | ||
"@typescript-eslint/parser": "^5.8.0", | ||
"coveralls": "^3.1.1", | ||
"eslint": "^8.5.0", | ||
@@ -64,3 +62,2 @@ "eslint-config-airbnb-base": "^15.0.0", | ||
"jest": "^27.4.5", | ||
"nyc": "^15.1.0", | ||
"ts-jest": "^27.1.2", | ||
@@ -71,19 +68,3 @@ "typescript": "^4.5.4", | ||
"ts-loader": "^9.2.6" | ||
}, | ||
"nyc": { | ||
"reporter": [ | ||
"text", | ||
"text-summary", | ||
"lcov", | ||
"html" | ||
], | ||
"include": [ | ||
"src/**/*.js" | ||
], | ||
"require": [ | ||
"babel-register" | ||
], | ||
"sourceMap": false, | ||
"instrument": true | ||
} | ||
} | ||
} |
@@ -1,4 +0,3 @@ | ||
[![Build Status][travis-image]][travis-url] | ||
[![Coverage Status][coveralls-image]][coveralls-url] | ||
[![NPM Version][npm-image]][npm-url] | ||
 | ||
@@ -29,3 +28,3 @@ | ||
```js | ||
import { waitFor } from 'poll-until-promise'; | ||
import { waitFor, AbortError } from 'poll-until-promise'; | ||
@@ -39,3 +38,3 @@ async function waitForDomElement(cssSelector = 'div') { | ||
}, { timeout: 60_000 }); | ||
return element; | ||
@@ -52,2 +51,8 @@ } catch (e) { | ||
const res = await fetch(path); | ||
// Stop immediately if the resource doesn't exist | ||
if (res.status === 404) { | ||
throw new AbortError(res.statusText); | ||
} | ||
return res.json(); | ||
@@ -112,2 +117,3 @@ }, { timeout: 60_000, interval: 1000 }); | ||
message: 'Waiting for time to pass :)', // custom message to display on failure | ||
maxAttempts: 5, // maximum attempts to make (default: no limit). Will still fail to wait if reaching timeout before attempts exhausted | ||
}; | ||
@@ -156,9 +162,3 @@ let pollUntilPromise = new PollUntil(options); | ||
[travis-url]: https://travis-ci.org/AlonMiz/poll-until-promise | ||
[travis-image]: https://travis-ci.org/AlonMiz/poll-until-promise.svg?branch=master | ||
[npm-url]: https://npmjs.org/package/poll-until-promise | ||
[npm-image]: https://img.shields.io/npm/v/poll-until-promise.svg | ||
[coveralls-url]: https://coveralls.io/github/AlonMiz/poll-until-promise | ||
[coveralls-image]: https://img.shields.io/coveralls/AlonMiz/poll-until-promise.svg |
export { | ||
IWaitForOptions, PollUntil, waitFor, IExecuteFunction, | ||
} from './poll-until-promise'; | ||
export { AbortError } from './abort'; |
@@ -0,1 +1,3 @@ | ||
import { AbortError } from './abort'; | ||
const ERRORS = { | ||
@@ -29,2 +31,3 @@ NOT_FUNCTION: 'Your executor is not a function. functions and promises are valid.', | ||
verbose?: boolean | ||
maxAttempts?: number | ||
} | ||
@@ -35,2 +38,3 @@ | ||
_timeout: number; | ||
_executedAttempts: number; | ||
private _stopOnFailure: boolean; | ||
@@ -43,2 +47,3 @@ private readonly _backoffFactor: number; | ||
private readonly _verbose: boolean; | ||
private readonly _maxAttempts: number | undefined; | ||
private _isWaiting: boolean; | ||
@@ -61,5 +66,7 @@ private _isResolved: boolean; | ||
message = '', | ||
maxAttempts, | ||
}:IWaitForOptions = {}) { | ||
this._interval = interval; | ||
this._timeout = timeout; | ||
this._executedAttempts = 0; | ||
this._stopOnFailure = stopOnFailure; | ||
@@ -74,2 +81,3 @@ this._isWaiting = false; | ||
this._backoffMaxInterval = backoffMaxInterval || timeout; | ||
this._maxAttempts = maxAttempts; | ||
this.start = +Date.now(); | ||
@@ -131,5 +139,9 @@ } | ||
_shouldStopTrying() { | ||
return this._timeFromStart() > this._timeout; | ||
return this._timeFromStart() > this._timeout || this._attemptsExhausted(); | ||
} | ||
_attemptsExhausted() { | ||
return this._maxAttempts !== undefined && this._executedAttempts >= this._maxAttempts; | ||
} | ||
_executeAgain() { | ||
@@ -140,2 +152,3 @@ this._log('executing again'); | ||
this._interval = (nextInterval > this._backoffMaxInterval) ? this._backoffMaxInterval : nextInterval; | ||
this._executedAttempts += 1; | ||
setTimeout(this._runFunction.bind(this), currentInterval); | ||
@@ -145,3 +158,6 @@ } | ||
_failedToWait() { | ||
let waitErrorText = `${ERRORS.FAILED_TO_WAIT} after ${this._timeFromStart()}ms`; | ||
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}`; | ||
@@ -182,2 +198,6 @@ if (this._lastError) { | ||
.catch((err: Error) => { | ||
if (err instanceof AbortError) { | ||
this._log(`aborted with err: ${err.cause}`); | ||
return this.reject?.(err.cause); | ||
} | ||
if (this._stopOnFailure) { | ||
@@ -184,0 +204,0 @@ this._log(`stopped on failure with err: ${err}`); |
47858
11.97%16
-11.11%21
23.53%984
13.89%