Skip to content

Commit

Permalink
fix: fix tryUntilAsync never-terminating behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
Marviel committed Aug 23, 2023
1 parent 90483d4 commit cdfa7f9
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 10 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lukebechtel/lab-ts-utils",
"version": "4.0.0",
"version": "6.0.0",
"description": "A library of small, self-contained utility functions for use in TypeScript projects.",
"main": "./lib/src/index.js",
"types": "./lib/src/index.d.ts",
Expand All @@ -18,8 +18,8 @@
"lint": "eslint ./src/ --fix",
"prepare": "husky install",
"semantic-release": "semantic-release",
"test:watch": "jest --forceExit --watch",
"test": "jest --forceExit --coverage",
"test:watch": "jest --watch",
"test": "jest --coverage",
"typecheck": "tsc --noEmit"
},
"repository": {
Expand Down
44 changes: 37 additions & 7 deletions src/functions/tryUntilAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,46 @@ export function tryUntilAsync<TReturn>(
// to the max value of a 32-bit int minus 2.
const usingTimeout = Math.min(maxTimeMS, Math.pow(2, 31) - 2)
const timeoutErrorMessage = `Timed out after maxTimeMS: ${maxTimeMS} ${usingTimeout === maxTimeMS ? '' : '(truncated to 2^31 - 2)'}`;
setTimeout(() => {

var outerTimeout: NodeJS.Timeout | undefined = undefined;
var innerTimeout: NodeJS.Timeout | undefined = undefined;
var delayTimeout: NodeJS.Timeout | number | undefined = undefined;

// Setup the outer timeout.
outerTimeout = setTimeout(() => {
reject(new TryUntilTimeoutError(timeoutErrorMessage, lastError));
}, usingTimeout);

const safeResolve = (resolveValue: TReturn | PromiseLike<TReturn>) => {
// Clear timeouts
if (outerTimeout) clearTimeout(outerTimeout);
if (innerTimeout) clearTimeout(innerTimeout);
if (delayTimeout) clearTimeout(delayTimeout);

// Resolve
resolve(resolveValue);
}

const safeReject = (rejectValue: Error) => {
// Clear timeouts
if (outerTimeout) clearTimeout(outerTimeout);
if (innerTimeout) clearTimeout(innerTimeout);
if (delayTimeout) clearTimeout(delayTimeout);

// Reject
reject(rejectValue);
}

while (true) {
try {
// Because setTimeout only accepts a 32-bit int, we need to limit the maxTimePerAttemptMS
// to the max value of a 32-bit int minus 2.
const usingAttemptTimeout = Math.min(maxTimePerAttemptMS, Math.pow(2, 31) - 2)
const timeoutAttemptErrorMessage = `Attempt timed out after maxTimeMS: ${usingAttemptTimeout} ${usingTimeout === usingAttemptTimeout ? '' : '(truncated to 2^31 - 2)'}`;
setTimeout(() => {

// Reset the timeout.
if (innerTimeout) clearTimeout(innerTimeout);
innerTimeout = setTimeout(() => {
throw new TryUntilTimeoutError(timeoutAttemptErrorMessage, lastError);
}, usingAttemptTimeout);

Expand All @@ -153,11 +181,11 @@ export function tryUntilAsync<TReturn>(

if (stopCondition && stopCondition(result)) {
// Check stop condition, if provided
resolve(result);
safeResolve(result);
return;
} else if (!stopCondition) {
// If no stop condition, resolve with result
resolve(result);
safeResolve(result);
return;
}
} catch (e: any) {
Expand All @@ -166,14 +194,14 @@ export function tryUntilAsync<TReturn>(

// If we're over our max attempts, reject.
if (maxAttempts && attempts >= maxAttempts - 1) {
reject(new TryUntilTimeoutError(`Exceeded maxAttempts: ${maxAttempts}`, lastError));
safeReject(new TryUntilTimeoutError(`Exceeded maxAttempts: ${maxAttempts}`, lastError));
return;
}

// If we're over our max time, reject.
// NOTE: this probably already happened due to the setTimeout above.
else if (maxTimeMS && Date.now() - startTime >= maxTimeMS) {
reject(new TryUntilTimeoutError(timeoutErrorMessage, lastError));
safeReject(new TryUntilTimeoutError(timeoutErrorMessage, lastError));
return;
}
}
Expand All @@ -183,7 +211,9 @@ export function tryUntilAsync<TReturn>(

// Wait for delay
if (delay.ms) {
await new Promise(resolve => setTimeout(resolve, delay.ms));
await new Promise(resolve => {
delayTimeout = setTimeout(resolve, delay.ms)
});
} else if (delay.delayFunction) {
await delay.delayFunction();
}
Expand Down

0 comments on commit cdfa7f9

Please sign in to comment.