Skip to content

Commit bff36bb

Browse files
committed
Stop decorating errors
Fixes #25
1 parent 1a81c1e commit bff36bb

File tree

5 files changed

+38
-39
lines changed

5 files changed

+38
-39
lines changed

index.d.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@ export class AbortError extends Error {
1010
constructor(message: string | Error);
1111
}
1212

13-
export type FailedAttemptError = {
13+
export type RetryContext = {
14+
readonly error: Error;
1415
readonly attemptNumber: number;
1516
readonly retriesLeft: number;
16-
} & Error;
17+
};
1718

1819
export type Options = {
1920
/**
20-
Callback invoked on each retry. Receives the error thrown by `input` as the first argument with properties `attemptNumber` and `retriesLeft` which indicate the current attempt number and the number of attempts left, respectively.
21+
Callback invoked on each retry. Receives a context object containing the error and retry state information.
2122
2223
@example
2324
```
@@ -34,8 +35,8 @@ export type Options = {
3435
};
3536
3637
const result = await pRetry(run, {
37-
onFailedAttempt: error => {
38-
console.log(`Attempt ${error.attemptNumber} failed. There are ${error.retriesLeft} retries left.`);
38+
onFailedAttempt: ({error, attemptNumber, retriesLeft}) => {
39+
console.log(`Attempt ${attemptNumber} failed. There are ${retriesLeft} retries left.`);
3940
// 1st request => Attempt 1 failed. There are 5 retries left.
4041
// 2nd request => Attempt 2 failed. There are 4 retries left.
4142
// …
@@ -56,7 +57,7 @@ export type Options = {
5657
const run = async () => { … };
5758
5859
const result = await pRetry(run, {
59-
onFailedAttempt: async error => {
60+
onFailedAttempt: async () => {
6061
console.log('Waiting for 1 second before retrying');
6162
await delay(1000);
6263
}
@@ -65,29 +66,27 @@ export type Options = {
6566
6667
If the `onFailedAttempt` function throws, all retries will be aborted and the original promise will reject with the thrown error.
6768
*/
68-
readonly onFailedAttempt?: (error: FailedAttemptError) => void | Promise<void>;
69+
readonly onFailedAttempt?: (context: RetryContext) => void | Promise<void>;
6970

7071
/**
71-
Decide if a retry should occur based on the error. Returning true triggers a retry, false aborts with the error.
72+
Decide if a retry should occur based on the context. Returning true triggers a retry, false aborts with the error.
7273
7374
It is not called for `TypeError` (except network errors) and `AbortError`.
7475
75-
@param error - The error thrown by the input function.
76-
7776
@example
7877
```
7978
import pRetry from 'p-retry';
8079
8180
const run = async () => { … };
8281
8382
const result = await pRetry(run, {
84-
shouldRetry: error => !(error instanceof CustomError);
83+
shouldRetry: ({error, attemptNumber, retriesLeft}) => !(error instanceof CustomError);
8584
});
8685
```
8786
8887
In the example above, the operation will be retried unless the error is an instance of `CustomError`.
8988
*/
90-
readonly shouldRetry?: (error: FailedAttemptError) => boolean | Promise<boolean>;
89+
readonly shouldRetry?: (context: RetryContext) => boolean | Promise<boolean>;
9190

9291
/**
9392
The maximum amount of times to retry the operation.
@@ -175,7 +174,6 @@ Does not retry on most `TypeErrors`, with the exception of network errors. This
175174
@example
176175
```
177176
import pRetry, {AbortError} from 'p-retry';
178-
import fetch from 'node-fetch';
179177
180178
const run = async () => {
181179
const response = await fetch('https://sindresorhus.com/unicorn');

index.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ export class AbortError extends Error {
1717
}
1818
}
1919

20-
const decorateErrorWithCounts = (error, attemptNumber, options) => {
20+
const createRetryContext = (error, attemptNumber, options) => {
2121
// Minus 1 from attemptNumber because the first attempt does not count as a retry
2222
const retriesLeft = options.retries - (attemptNumber - 1);
2323

24-
error.attemptNumber = attemptNumber;
25-
error.retriesLeft = retriesLeft;
26-
return error;
24+
return Object.freeze({
25+
error,
26+
attemptNumber,
27+
retriesLeft,
28+
});
2729
};
2830

2931
function calculateDelay(attempt, options) {
@@ -87,16 +89,16 @@ export default async function pRetry(input, options = {}) {
8789
throw error;
8890
}
8991

90-
decorateErrorWithCounts(error, attemptNumber, options);
92+
const context = createRetryContext(error, attemptNumber, options);
9193

9294
// Always call onFailedAttempt
93-
await options.onFailedAttempt(error);
95+
await options.onFailedAttempt(context);
9496

9597
const currentTime = Date.now();
9698
if (
9799
currentTime - startTime >= maxRetryTime
98100
|| attemptNumber >= options.retries + 1
99-
|| !(await options.shouldRetry(error))
101+
|| !(await options.shouldRetry(context))
100102
) {
101103
throw error; // Do not retry, throw the original error
102104
}

index.test-d.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {expectType} from 'tsd';
2-
import pRetry, {AbortError, type FailedAttemptError} from './index.js';
2+
import pRetry, {AbortError, type RetryContext} from './index.js';
33

44
expectType<Promise<number>>(
55
pRetry(async count => {
@@ -9,10 +9,10 @@ expectType<Promise<number>>(
99
);
1010
expectType<Promise<void>>(
1111
pRetry(() => {}, { // eslint-disable-line @typescript-eslint/no-empty-function
12-
onFailedAttempt(error) {
13-
expectType<FailedAttemptError>(error);
14-
expectType<number>(error.attemptNumber);
15-
expectType<number>(error.retriesLeft);
12+
onFailedAttempt(context) {
13+
expectType<RetryContext>(context);
14+
expectType<number>(context.attemptNumber);
15+
expectType<number>(context.retriesLeft);
1616
},
1717
}),
1818
);

readme.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ npm install p-retry
1414

1515
```js
1616
import pRetry, {AbortError} from 'p-retry';
17-
import fetch from 'node-fetch';
1817

1918
const run = async () => {
2019
const response = await fetch('https://sindresorhus.com/unicorn');
@@ -48,11 +47,11 @@ Receives the number of attempts as the first argument and is expected to return
4847

4948
Type: `object`
5049

51-
##### onFailedAttempt(error)
50+
##### onFailedAttempt(context)
5251

5352
Type: `Function`
5453

55-
Callback invoked on each retry. Receives the error thrown by `input` as the first argument with properties `attemptNumber` and `retriesLeft` which indicate the current attempt number and the number of attempts left, respectively.
54+
Callback invoked on each retry. Receives a context object containing the error and retry state information.
5655

5756
```js
5857
import pRetry from 'p-retry';
@@ -68,8 +67,8 @@ const run = async () => {
6867
};
6968

7069
const result = await pRetry(run, {
71-
onFailedAttempt: error => {
72-
console.log(`Attempt ${error.attemptNumber} failed. There are ${error.retriesLeft} retries left.`);
70+
onFailedAttempt: ({error, attemptNumber, retriesLeft}) => {
71+
console.log(`Attempt ${attemptNumber} failed. There are ${retriesLeft} retries left.`);
7372
// 1st request => Attempt 1 failed. There are 5 retries left.
7473
// 2nd request => Attempt 2 failed. There are 4 retries left.
7574
//
@@ -89,7 +88,7 @@ import delay from 'delay';
8988
const run = async () => { … };
9089

9190
const result = await pRetry(run, {
92-
onFailedAttempt: async error => {
91+
onFailedAttempt: async () => {
9392
console.log('Waiting for 1 second before retrying');
9493
await delay(1000);
9594
}
@@ -98,11 +97,11 @@ const result = await pRetry(run, {
9897

9998
If the `onFailedAttempt` function throws, all retries will be aborted and the original promise will reject with the thrown error.
10099

101-
##### shouldRetry(error)
100+
##### shouldRetry(context)
102101

103102
Type: `Function`
104103

105-
Decide if a retry should occur based on the error. Returning true triggers a retry, false aborts with the error.
104+
Decide if a retry should occur based on the context. Returning true triggers a retry, false aborts with the error.
106105

107106
It is not called for `TypeError` (except network errors) and `AbortError`.
108107

@@ -112,7 +111,7 @@ import pRetry from 'p-retry';
112111
const run = async () => { … };
113112

114113
const result = await pRetry(run, {
115-
shouldRetry: error => !(error instanceof CustomError);
114+
shouldRetry: ({error, attemptNumber, retriesLeft}) => !(error instanceof CustomError);
116115
});
117116
```
118117

test.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ test('operation stops immediately on AbortError', async t => {
151151
// let index = 0;
152152
// const controller = new AbortController();
153153

154-
// await t.throwsAsync(pRetry(async attemptNumber => {
154+
// await t.throwsAsync(pRetry(async ({attemptNumber}) => {
155155
// await delay(40);
156156
// index++;
157157
// if (attemptNumber === 3) {
@@ -201,7 +201,7 @@ test('shouldRetry controls retry behavior', async t => {
201201
const error = index < 3 ? shouldRetryError : customError;
202202
throw error;
203203
}, {
204-
async shouldRetry(error) {
204+
async shouldRetry({error}) {
205205
return error.message === shouldRetryError.message;
206206
},
207207
retries: 10,
@@ -248,10 +248,10 @@ test('onFailedAttempt provides correct error details', async t => {
248248
return attemptNumber === 3 ? fixture : Promise.reject(fixtureError);
249249
},
250250
{
251-
onFailedAttempt(error) {
251+
onFailedAttempt({error, attemptNumber: attempt, retriesLeft}) {
252252
t.is(error, fixtureError);
253-
t.is(error.attemptNumber, ++attemptNumber);
254-
t.is(error.retriesLeft, retries - (index - 1));
253+
t.is(attempt, ++attemptNumber);
254+
t.is(retriesLeft, retries - (index - 1));
255255
},
256256
retries,
257257
},

0 commit comments

Comments
 (0)