Skip to content

Commit

Permalink
Require Node.js 12.20 and move to ESM
Browse files Browse the repository at this point in the history
  • Loading branch information
sindresorhus committed Nov 3, 2021
1 parent e7f9f61 commit 8c8a86c
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 229 deletions.
3 changes: 0 additions & 3 deletions .github/funding.yml

This file was deleted.

5 changes: 2 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@ jobs:
fail-fast: false
matrix:
node-version:
- 16
- 14
- 12
- 10
- 8
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- run: npm install
Expand Down
139 changes: 61 additions & 78 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,106 +1,89 @@
import {OperationOptions} from 'retry';

declare class AbortErrorClass extends Error {
export class AbortError extends Error {
readonly name: 'AbortError';
readonly originalError: Error;

/**
Abort retrying and reject the promise.
@param message - Error message or custom error.
@param message - An error message or a custom error.
*/
constructor(message: string | Error);
}

declare namespace pRetry {
interface FailedAttemptError extends Error {
readonly attemptNumber: number;
readonly retriesLeft: number;
}

interface Options extends OperationOptions {
/**
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.
export interface FailedAttemptError extends Error {
readonly attemptNumber: number;
readonly retriesLeft: number;
}

The `onFailedAttempt` function can return a promise. For example, to add a [delay](https://github.com/sindresorhus/delay):
export interface Options extends OperationOptions {
/**
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.
```
import pRetry = require('p-retry');
import delay = require('delay');
The `onFailedAttempt` function can return a promise. For example, to add a [delay](https://github.com/sindresorhus/delay):
const run = async () => { ... };
```
import pRetry from 'p-retry';
import delay from 'delay';
(async () => {
const result = await pRetry(run, {
onFailedAttempt: async error => {
console.log('Waiting for 1 second before retrying');
await delay(1000);
}
});
})();
```
const run = async () => { ... };
If the `onFailedAttempt` function throws, all retries will be aborted and the original promise will reject with the thrown error.
*/
readonly onFailedAttempt?: (error: FailedAttemptError) => void | Promise<void>;
}
const result = await pRetry(run, {
onFailedAttempt: async error => {
console.log('Waiting for 1 second before retrying');
await delay(1000);
}
});
```
type AbortError = AbortErrorClass;
If the `onFailedAttempt` function throws, all retries will be aborted and the original promise will reject with the thrown error.
*/
readonly onFailedAttempt?: (error: FailedAttemptError) => void | Promise<void>;
}

declare const pRetry: {
/**
Returns a `Promise` that is fulfilled when calling `input` returns a fulfilled promise. If calling `input` returns a rejected promise, `input` is called again until the max retries are reached, it then rejects with the last rejection reason.
Does not retry on most `TypeErrors`, with the exception of network errors. This is done on a best case basis as different browsers have different [messages](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful) to indicate this.
See [whatwg/fetch#526 (comment)](https://github.com/whatwg/fetch/issues/526#issuecomment-554604080)
/**
Returns a `Promise` that is fulfilled when calling `input` returns a fulfilled promise. If calling `input` returns a rejected promise, `input` is called again until the max retries are reached, it then rejects with the last rejection reason.
@param input - Receives the number of attempts as the first argument and is expected to return a `Promise` or any value.
@param options - Options are passed to the [`retry`](https://github.com/tim-kos/node-retry#retryoperationoptions) module.
@example
```
import pRetry = require('p-retry');
import fetch from 'node-fetch';
Does not retry on most `TypeErrors`, with the exception of network errors. This is done on a best case basis as different browsers have different [messages](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful) to indicate this.
See [whatwg/fetch#526 (comment)](https://github.com/whatwg/fetch/issues/526#issuecomment-554604080)
const run = async () => {
const response = await fetch('https://sindresorhus.com/unicorn');
@param input - Receives the number of attempts as the first argument and is expected to return a `Promise` or any value.
@param options - Options are passed to the [`retry`](https://github.com/tim-kos/node-retry#retryoperationoptions) module.
// Abort retrying if the resource doesn't exist
if (response.status === 404) {
throw new pRetry.AbortError(response.statusText);
}
@example
```
import pRetry from 'p-retry';
import fetch from 'node-fetch';
return response.blob();
};
(async () => {
console.log(await pRetry(run, {retries: 5}));
// With the `onFailedAttempt` option:
const result = await pRetry(run, {
onFailedAttempt: error => {
console.log(`Attempt ${error.attemptNumber} failed. There are ${error.retriesLeft} retries left.`);
// 1st request => Attempt 1 failed. There are 4 retries left.
// 2nd request => Attempt 2 failed. There are 3 retries left.
// …
},
retries: 5
});
console.log(result);
})();
```
*/
<T>(
input: (attemptCount: number) => PromiseLike<T> | T,
options?: pRetry.Options
): Promise<T>;
const run = async () => {
const response = await fetch('https://sindresorhus.com/unicorn');
AbortError: typeof AbortErrorClass;
// Abort retrying if the resource doesn't exist
if (response.status === 404) {
throw new pRetry.AbortError(response.statusText);
}
// TODO: remove this in the next major version
default: typeof pRetry;
return response.blob();
};
export = pRetry;
console.log(await pRetry(run, {retries: 5}));
// With the `onFailedAttempt` option:
const result = await pRetry(run, {
onFailedAttempt: error => {
console.log(`Attempt ${error.attemptNumber} failed. There are ${error.retriesLeft} retries left.`);
// 1st request => Attempt 1 failed. There are 4 retries left.
// 2nd request => Attempt 2 failed. There are 3 retries left.
// …
},
retries: 5
});
console.log(result);
```
*/
export default function pRetry<T>(
input: (attemptCount: number) => PromiseLike<T> | T,
options?: Options
): Promise<T>;
87 changes: 41 additions & 46 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
'use strict';
const retry = require('retry');
import retry from 'retry';

const networkErrorMsgs = [
const networkErrorMsgs = new Set([
'Failed to fetch', // Chrome
'NetworkError when attempting to fetch resource.', // Firefox
'The Internet connection appears to be offline.', // Safari
'Network request failed' // `cross-fetch`
];
'Network request failed', // `cross-fetch`
]);

class AbortError extends Error {
export class AbortError extends Error {
constructor(message) {
super();

Expand All @@ -34,52 +33,48 @@ const decorateErrorWithCounts = (error, attemptNumber, options) => {
return error;
};

const isNetworkError = errorMessage => networkErrorMsgs.includes(errorMessage);
const isNetworkError = errorMessage => networkErrorMsgs.has(errorMessage);

const pRetry = (input, options) => new Promise((resolve, reject) => {
options = {
onFailedAttempt: () => {},
retries: 10,
...options
};
export default async function pRetry(input, options) {
return new Promise((resolve, reject) => {
options = {
onFailedAttempt: () => {},
retries: 10,
...options,
};

const operation = retry.operation(options);
const operation = retry.operation(options);

operation.attempt(async attemptNumber => {
try {
resolve(await input(attemptNumber));
} catch (error) {
if (!(error instanceof Error)) {
reject(new TypeError(`Non-error was thrown: "${error}". You should only throw errors.`));
return;
}

if (error instanceof AbortError) {
operation.stop();
reject(error.originalError);
} else if (error instanceof TypeError && !isNetworkError(error.message)) {
operation.stop();
reject(error);
} else {
decorateErrorWithCounts(error, attemptNumber, options);

try {
await options.onFailedAttempt(error);
} catch (error) {
reject(error);
operation.attempt(async attemptNumber => {
try {
resolve(await input(attemptNumber));
} catch (error) {
if (!(error instanceof Error)) {
reject(new TypeError(`Non-error was thrown: "${error}". You should only throw errors.`));
return;
}

if (!operation.retry(error)) {
reject(operation.mainError());
if (error instanceof AbortError) {
operation.stop();
reject(error.originalError);
} else if (error instanceof TypeError && !isNetworkError(error.message)) {
operation.stop();
reject(error);
} else {
decorateErrorWithCounts(error, attemptNumber, options);

try {
await options.onFailedAttempt(error);
} catch (error) {
reject(error);
return;
}

if (!operation.retry(error)) {
reject(operation.mainError());
}
}
}
}
});
});
});

module.exports = pRetry;
// TODO: remove this in the next major version
module.exports.default = pRetry;

module.exports.AbortError = AbortError;
}
20 changes: 9 additions & 11 deletions index.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
import {expectType} from 'tsd';
import pRetry = require('.');
import {AbortError, FailedAttemptError} from '.';
import pRetry, {AbortError, FailedAttemptError} from './index.js';

expectType<Promise<number>>(
pRetry(count => {
pRetry(async count => {
expectType<number>(count);
return Promise.resolve(1);
})
}),
);
expectType<Promise<void>>(
pRetry(() => {}, {
pRetry(() => undefined, {
onFailedAttempt: error => {
expectType<FailedAttemptError>(error);
expectType<number>(error.attemptNumber);
expectType<number>(error.retriesLeft);
}
})
},
}),
);
expectType<Promise<string>>(
pRetry(() => 'foo', {
retries: 5
})
retries: 5,
}),
);

const abortError = new AbortError('foo');
new AbortError(new Error('foo'));
new AbortError(new Error('foo')); // eslint-disable-line no-new

abortError instanceof AbortError;
expectType<AbortError>(abortError);
2 changes: 1 addition & 1 deletion license
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

Expand Down
17 changes: 10 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
"description": "Retry a promise-returning or async function",
"license": "MIT",
"repository": "sindresorhus/p-retry",
"funding": "https://github.com/sponsors/sindresorhus",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
"url": "https://sindresorhus.com"
},
"type": "module",
"exports": "./index.js",
"engines": {
"node": ">=8"
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"scripts": {
"test": "xo && ava && tsd"
Expand Down Expand Up @@ -39,13 +42,13 @@
"bluebird"
],
"dependencies": {
"@types/retry": "^0.12.0",
"@types/retry": "^0.12.1",
"retry": "^0.13.1"
},
"devDependencies": {
"ava": "^2.4.0",
"delay": "^4.1.0",
"tsd": "^0.10.0",
"xo": "^0.25.3"
"ava": "^3.15.0",
"delay": "^5.0.0",
"tsd": "^0.18.0",
"xo": "^0.46.4"
}
}

0 comments on commit 8c8a86c

Please sign in to comment.