Skip to content

Commit

Permalink
Add ability to stop retries in beforeRetry hooks (#198)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
whitecrownclown and sindresorhus committed Nov 15, 2019
1 parent c0d9d2b commit 0bc6fb1
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 1 deletion.
26 changes: 26 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,32 @@ declare const ky: {
@returns A new Ky instance.
*/
extend(defaultOptions: Options): typeof ky;

/**
A `Symbol` that can be returned by a `beforeRetry` hook to stop the retry.
This will also short circuit the remaining `beforeRetry` hooks.
@example
```
import ky from 'ky';
(async () => {
await ky('https://example.com', {
hooks: {
beforeRetry: [
async (request, options, errors, retryCount) => {
const shouldStopRetry = await ky('https://example.com/api');
if (shouldStopRetry) {
return ky.stop;
}
}
]
}
});
})();
```
*/
readonly stop: unique symbol;
};

export default ky;
10 changes: 9 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ const retryAfterStatusCodes = [
503
];

const stop = Symbol('stop');

class HTTPError extends Error {
constructor(response) {
super(response.statusText);
Expand Down Expand Up @@ -354,12 +356,17 @@ class Ky {

for (const hook of this._options.hooks.beforeRetry) {
// eslint-disable-next-line no-await-in-loop
await hook(
const hookResult = await hook(
this.request,
this._options,
error,
this._retryCount,
);

// If `stop` is returned from the hook, the retry process is stopped
if (hookResult === stop) {
return;
}
}

return this._retry(fn);
Expand Down Expand Up @@ -450,6 +457,7 @@ const createInstance = defaults => {

ky.create = newDefaults => createInstance(validateAndMerge(newDefaults));
ky.extend = newDefaults => createInstance(validateAndMerge(defaults, newDefaults));
ky.stop = stop;

return ky;
};
Expand Down
1 change: 1 addition & 0 deletions index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ expectType<typeof ky>(ky.create({}));
expectType<typeof ky>(ky.extend({}));
expectType<HTTPError>(new HTTPError(new Response));
expectType<TimeoutError>(new TimeoutError);
expectType<symbol>(ky.stop);

ky(url, {
hooks: {
Expand Down
23 changes: 23 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,29 @@ Exposed for `instanceof` checks. The error has a `response` property with the [`

The error thrown when the request times out.

### ky.stop

A `Symbol` that can be returned by a `beforeRetry` hook to stop the retry. This will also short circuit the remaining `beforeRetry` hooks.

```js
import ky from 'ky';

(async () => {
await ky('https://example.com', {
hooks: {
beforeRetry: [
async (request, options, errors, retryCount) => {
const shouldStopRetry = await ky('https://example.com/api');
if (shouldStopRetry) {
return ky.stop;
}
}
]
}
});
})();
```


## Tips

Expand Down
32 changes: 32 additions & 0 deletions test/hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,38 @@ test('beforeRetry hook is called with error and retryCount', async t => {
await server.close();
});

test('beforeRetry hook can cancel retries by returning `stop`', async t => {
let requestCount = 0;

const server = await createTestServer();
server.get('/', async (request, response) => {
requestCount++;

if (requestCount > 2) {
response.end(request.headers.unicorn);
} else {
response.sendStatus(408);
}
});

await ky.get(server.url, {
hooks: {
beforeRetry: [
(_input, options, error, retryCount) => {
t.truthy(error);
t.is(retryCount, 1);

return ky.stop;
}
]
}
});

t.is(requestCount, 1);

await server.close();
});

test('catches beforeRetry thrown errors', async t => {
let requestCount = 0;

Expand Down

0 comments on commit 0bc6fb1

Please sign in to comment.