From df5b45971a6bf3f8b1cbfe0b77d47a6c2d568063 Mon Sep 17 00:00:00 2001 From: tim-kos Date: Mon, 19 Oct 2026 17:24:24 +0200 Subject: [PATCH 1/2] Add retryOperation.nextTimeout() to tell how long until the next retry Co-Authored-By: Claude Opus 5.5 --- README.md | 17 +++++++++++++++- lib/retry_operation.js | 13 +++++++++++- test/integration/test-retry-operation.js | 25 ++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 976f688..c4a3058 100644 --- a/README.md +++ b/README.md @@ -228,7 +228,22 @@ Returns `false` when no `error` value is given, or the maximum amount of retries has been reached. Otherwise it returns `true`, and retries the operation after the timeout for -the current attempt number. +the current attempt number. `retryOperation.nextTimeout()` tells you how long +that timeout is. + +#### retryOperation.nextTimeout() + +Returns the number of milliseconds the operation waits before the retry that the +last `retryOperation.retry()` call scheduled. Returns `null` if that call did not +schedule a retry, or if no retry has been scheduled since the operation was +created, `reset()` or `stop()`ped. + +```javascript +if (operation.retry(err)) { + console.warn('Request failed, retrying in ' + operation.nextTimeout() + 'ms'); + return; +} +``` #### retryOperation.stop() diff --git a/lib/retry_operation.js b/lib/retry_operation.js index 105ce72..5f2b2f1 100644 --- a/lib/retry_operation.js +++ b/lib/retry_operation.js @@ -16,6 +16,7 @@ function RetryOperation(timeouts, options) { this._timeout = null; this._operationStart = null; this._timer = null; + this._nextTimeout = null; if (this._options.forever) { this._cachedTimeouts = this._timeouts.slice(0); @@ -26,6 +27,7 @@ module.exports = RetryOperation; RetryOperation.prototype.reset = function() { this._attempts = 1; this._timeouts = this._originalTimeouts.slice(0); + this._nextTimeout = null; } RetryOperation.prototype.stop = function() { @@ -38,6 +40,7 @@ RetryOperation.prototype.stop = function() { this._timeouts = []; this._cachedTimeouts = null; + this._nextTimeout = null; }; RetryOperation.prototype.retry = function(err) { @@ -45,6 +48,8 @@ RetryOperation.prototype.retry = function(err) { clearTimeout(this._timeout); } + this._nextTimeout = null; + if (!err) { return false; } @@ -62,12 +67,14 @@ RetryOperation.prototype.retry = function(err) { if (this._cachedTimeouts) { // retry forever, only keep last error this._errors.splice(0, this._errors.length - 1); - timeout = this._cachedTimeouts.slice(-1); + timeout = this._cachedTimeouts[this._cachedTimeouts.length - 1]; } else { return false; } } + this._nextTimeout = timeout; + var self = this; this._timer = setTimeout(function() { self._attempts++; @@ -136,6 +143,10 @@ RetryOperation.prototype.attempts = function() { return this._attempts; }; +RetryOperation.prototype.nextTimeout = function() { + return this._nextTimeout; +}; + RetryOperation.prototype.mainError = function() { if (this._errors.length === 0) { return null; diff --git a/test/integration/test-retry-operation.js b/test/integration/test-retry-operation.js index 1176d3e..5ec5f86 100644 --- a/test/integration/test-retry-operation.js +++ b/test/integration/test-retry-operation.js @@ -291,3 +291,28 @@ var retry = require(common.dir.lib + '/retry'); fn(); })(); + +(function testNextTimeout() { + var operation = retry.operation([10, 20]); + assert.strictEqual(operation.nextTimeout(), null); + + operation.attempt(function() {}); + assert.strictEqual(operation.retry(new Error('foo')), true); + assert.strictEqual(operation.nextTimeout(), 10); + assert.strictEqual(operation.retry(new Error('foo')), true); + assert.strictEqual(operation.nextTimeout(), 20); + assert.strictEqual(operation.retry(new Error('foo')), false); + assert.strictEqual(operation.nextTimeout(), null); + operation.stop(); +})(); + +(function testNextTimeoutWhenRetryingForever() { + var operation = retry.operation({retries: 1, minTimeout: 10, forever: true}); + + operation.attempt(function() {}); + operation.retry(new Error('foo')); + operation.retry(new Error('foo')); + assert.strictEqual(operation.nextTimeout(), 10); + operation.stop(); + assert.strictEqual(operation.nextTimeout(), null); +})(); From 1cad244427163f5890110ed85f7faa428dfa918c Mon Sep 17 00:00:00 2001 From: tim-kos Date: Mon, 19 Oct 2026 17:38:17 +0200 Subject: [PATCH 2/2] Test nextTimeout() through real attempts in its own file and note when to call it Co-Authored-By: Claude Opus 5.5 --- README.md | 3 +- test/integration/test-next-timeout.js | 36 ++++++++++++++++++++++++ test/integration/test-retry-operation.js | 25 ---------------- 3 files changed, 38 insertions(+), 26 deletions(-) create mode 100644 test/integration/test-next-timeout.js diff --git a/README.md b/README.md index c4a3058..277aae0 100644 --- a/README.md +++ b/README.md @@ -236,7 +236,8 @@ that timeout is. Returns the number of milliseconds the operation waits before the retry that the last `retryOperation.retry()` call scheduled. Returns `null` if that call did not schedule a retry, or if no retry has been scheduled since the operation was -created, `reset()` or `stop()`ped. +created, `reset()` or `stop()`ped. Call it right after `retry()`: inside the next +attempt it still returns the timeout that led up to that attempt. ```javascript if (operation.retry(err)) { diff --git a/test/integration/test-next-timeout.js b/test/integration/test-next-timeout.js new file mode 100644 index 0000000..1304c8b --- /dev/null +++ b/test/integration/test-next-timeout.js @@ -0,0 +1,36 @@ +var common = require('../common'); +var assert = common.assert; +var retry = require(common.dir.lib + '/retry'); + +(function testNextTimeout() { + var operation = retry.operation([1, 2]); + var nextTimeouts = []; + assert.strictEqual(operation.nextTimeout(), null); + + operation.attempt(function() { + operation.retry(new Error('foo')); + nextTimeouts.push(operation.nextTimeout()); + }); + + process.on('exit', function() { + assert.deepStrictEqual(nextTimeouts, [1, 2, null]); + }); +})(); + +(function testNextTimeoutWhenRetryingForever() { + var operation = retry.operation({retries: 1, minTimeout: 1, forever: true}); + var nextTimeouts = []; + + operation.attempt(function(attempt) { + operation.retry(new Error('foo')); + nextTimeouts.push(operation.nextTimeout()); + if (attempt === 3) { + operation.stop(); + nextTimeouts.push(operation.nextTimeout()); + } + }); + + process.on('exit', function() { + assert.deepStrictEqual(nextTimeouts, [1, 1, 1, null]); + }); +})(); diff --git a/test/integration/test-retry-operation.js b/test/integration/test-retry-operation.js index 5ec5f86..1176d3e 100644 --- a/test/integration/test-retry-operation.js +++ b/test/integration/test-retry-operation.js @@ -291,28 +291,3 @@ var retry = require(common.dir.lib + '/retry'); fn(); })(); - -(function testNextTimeout() { - var operation = retry.operation([10, 20]); - assert.strictEqual(operation.nextTimeout(), null); - - operation.attempt(function() {}); - assert.strictEqual(operation.retry(new Error('foo')), true); - assert.strictEqual(operation.nextTimeout(), 10); - assert.strictEqual(operation.retry(new Error('foo')), true); - assert.strictEqual(operation.nextTimeout(), 20); - assert.strictEqual(operation.retry(new Error('foo')), false); - assert.strictEqual(operation.nextTimeout(), null); - operation.stop(); -})(); - -(function testNextTimeoutWhenRetryingForever() { - var operation = retry.operation({retries: 1, minTimeout: 10, forever: true}); - - operation.attempt(function() {}); - operation.retry(new Error('foo')); - operation.retry(new Error('foo')); - assert.strictEqual(operation.nextTimeout(), 10); - operation.stop(); - assert.strictEqual(operation.nextTimeout(), null); -})();