From edcac6f6a0716c407755e0b2cb3b9452837c1eeb Mon Sep 17 00:00:00 2001 From: Daniel Eriksson Date: Thu, 21 Mar 2024 11:20:10 +0100 Subject: [PATCH 1/3] feat: add initialJitter --- README.md | 8 +++++ src/index.ts | 8 +++++ test/index.test.ts | 74 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/README.md b/README.md index 1903cc4..c666cac 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ The following object shows the default options: factor: 0, timeout: 0, jitter: false, + initialJitter: false, handleError: null, handleTimeout: null, beforeAttempt: null, @@ -155,6 +156,13 @@ to your target environment. (default: `false`) +- **`initialJitter`**: `Boolean` + + If `initialJitter` is `true` then a `jitter` will also be used in the + first call attempt. + + (default: `false`) + - **`minDelay`**: `Number` `minDelay` is used to set a lower bound of delay diff --git a/src/index.ts b/src/index.ts index e23128c..535bbd3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,6 +20,7 @@ export interface AttemptOptions { readonly maxAttempts: number; readonly timeout: number; readonly jitter: boolean; + readonly initialJitter: boolean; readonly handleError: HandleError | null; readonly handleTimeout: HandleTimeout | null; readonly beforeAttempt: BeforeAttempt | null; @@ -44,6 +45,7 @@ function applyDefaults (options?: PartialAttemptOptions): AttemptOptions ( await sleep(initialDelay); } + if (context.attemptNum < 1 && options.initialJitter) { + const delay = calculateDelay(context, options); + if (delay) { + await sleep(delay); + } + } return makeAttempt(); } diff --git a/test/index.test.ts b/test/index.test.ts index 2328d55..daaca4b 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -18,6 +18,7 @@ test('should be able to calculate delays', (t) => { maxAttempts: 0, timeout: 0, jitter: false, + initialJitter: false, handleError: null, handleTimeout: null, beforeAttempt: null, @@ -87,6 +88,7 @@ test('should default to 3 attempts with 200 delay', async (t) => { maxAttempts: 3, timeout: 0, jitter: false, + initialJitter: false, handleError: null, handleTimeout: null, beforeAttempt: null, @@ -334,6 +336,39 @@ test('should support jitter', async (t) => { }); }); +test('should support jitter with initialJitter', async (t) => { + let expectedDelays = [ + 0, + 100, + 200, + 400, + 800 + ]; + + let lastTime = Date.now(); + + return retry(async (context) => { + let newTime = Date.now(); + let actualDelay = newTime - lastTime; + lastTime = newTime; + + t.true(actualDelay <= (expectedDelays[context.attemptNum] + DELAY_TOLERANCE)); + t.true(actualDelay > 0); + + if (context.attemptsRemaining === 0) { + return 'success'; + } else { + throw new Error('try again'); + } + }, { + maxAttempts: expectedDelays.length, + delay: 100, + factor: 2, + jitter: true, + initialJitter: true + }); +}); + test('should support jitter with minDelay', async (t) => { let expectedDelays = [ 0, @@ -371,6 +406,45 @@ test('should support jitter with minDelay', async (t) => { }); }); +test('should support jitter with minDelay and initialJitter', async (t) => { + let expectedDelays = [ + 0, + 100, + 200, + 400, + 800 + ]; + + let lastTime = Date.now(); + const minDelay = 100; + + return retry(async (context) => { + let newTime = Date.now(); + let actualDelay = newTime - lastTime; + lastTime = newTime; + + if (context.attemptNum > 0) { + t.true(actualDelay >= minDelay); + } + + t.true(actualDelay <= (expectedDelays[context.attemptNum] + DELAY_TOLERANCE)); + t.true(actualDelay > 0); + + if (context.attemptsRemaining === 0) { + return 'success'; + } else { + throw new Error('try again'); + } + }, { + maxAttempts: expectedDelays.length, + delay: 100, + minDelay, + factor: 2, + jitter: true, + initialJitter: true + }); +}); + test('should detect invalid minDelay', async (t) => { const err = await t.throws(retry(async (context) => { throw new Error('should not get here'); From c1a7cd05e37bb23e229f8682acaa8656ac0c960d Mon Sep 17 00:00:00 2001 From: Daniel Eriksson Date: Thu, 21 Mar 2024 14:50:53 +0100 Subject: [PATCH 2/3] fix: properly await setTimeout --- src/index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 535bbd3..ab8834b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -54,9 +54,7 @@ function applyDefaults (options?: PartialAttemptOptions): AttemptOptions { - setTimeout(resolve, delay); - }); + return new Promise((resolve) => setTimeout(resolve, delay)); } export function defaultCalculateDelay (context: AttemptContext, options: AttemptOptions): number { From 8ccea636e36a69ce6a61a8facd88e6954c8ddc8d Mon Sep 17 00:00:00 2001 From: Daniel Eriksson Date: Thu, 21 Mar 2024 15:32:06 +0100 Subject: [PATCH 3/3] test: update test --- test/index.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/index.test.ts b/test/index.test.ts index daaca4b..b827fcd 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -353,7 +353,6 @@ test('should support jitter with initialJitter', async (t) => { lastTime = newTime; t.true(actualDelay <= (expectedDelays[context.attemptNum] + DELAY_TOLERANCE)); - t.true(actualDelay > 0); if (context.attemptsRemaining === 0) { return 'success'; @@ -428,7 +427,6 @@ test('should support jitter with minDelay and initialJitter', async (t) => { } t.true(actualDelay <= (expectedDelays[context.attemptNum] + DELAY_TOLERANCE)); - t.true(actualDelay > 0); if (context.attemptsRemaining === 0) { return 'success';