Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 24 additions & 9 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -259,23 +259,38 @@ A global `default` handler catches anything that no other handler does. A reques

## Retries

The http client can retry if a network error is encountered. The default is `retry: false`, and requests won't be retried.
Retries are on by default (since 13.0.0). Configure them with the `retry` option:

Configure it as follows:
```js
retry: {
attempts: 1, // retries after the first request
errors: [ 'ECONNRESET' ], // Node network error codes to retry on
methods: [ 'GET', 'HEAD', 'OPTIONS' ] // HTTP methods that may be retried
}
```

These are the defaults. Leave `retry` out to use them, pass only the settings you want to change, or pass `false` to never retry:

```js
import Api from '@beyonk/http'

Api.configure({
retry: {
attempts: 3 // How many times to retry before giving up
errors: [ 'ECONNRESET' ] // A list of error codes
}
})
Api.configure({}) // defaults: 1 retry on ECONNRESET for GET, HEAD, OPTIONS
Api.configure({ retry: { attempts: 2 } }) // up to 2 retries, other settings unchanged
Api.configure({ retry: { errors: [ 'ECONNRESET', 'EPIPE' ] } }) // also retry EPIPE
Api.configure({ retry: false }) // never retry
```

errors is an array of any number of the [nodejs network error codes](https://nodejs.org/api/errors.html#errors_common_system_errors)
| Setting | Meaning | Default |
| --- | --- | --- |
| `attempts` | Number of retries after the first request. `1` sends at most 2 requests, `0` never retries | `1` |
| `errors` | [Node network error codes](https://nodejs.org/api/errors.html#errors_common_system_errors) to retry on, matched on the error or its `cause` | `[ 'ECONNRESET' ]` |
| `methods` | HTTP methods that may be retried. Only add a method if repeating it is safe for every endpoint | `[ 'GET', 'HEAD', 'OPTIONS' ]` |

Good to know:

- Timeouts and HTTP error responses (4xx, 5xx) aren't retried.
- Retries only apply server-side, as browsers don't expose network error codes.
- Each retry logs a warning, e.g. `Got ECONNRESET when calling https://…/api/v1/apps. Retrying request (1/1)`.

## Parsing error payloads

Expand Down
32 changes: 27 additions & 5 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,24 @@ function getErrorByCode (code) {
return errorMapping[code] || HttpError
}

/**
* @param {ApiOptions['retry']} retry
* @returns {{ attempts: number, errors: string[], methods: string[] }}
*/
function retrySettings (retry) {
if (retry === false) {
return { attempts: 0, errors: [], methods: [] }
}

const {
attempts = 1,
errors = [ 'ECONNRESET' ],
methods = [ 'GET', 'HEAD', 'OPTIONS' ]
} = retry || {}

return { attempts, errors, methods }
}

/** @type {RequestConfig} */
const DEFAULT_CONFIG = {
endpoint: null,
Expand Down Expand Up @@ -130,7 +148,6 @@ class Api {
constructor (options) {
/** @type {ApiOptions} */
this.options = Object.assign({
retry: false,
parseErrors: true,
handlers: {}
}, options)
Expand Down Expand Up @@ -284,7 +301,6 @@ class Api {
* @throws {HttpError} If the request fails
*/
async #doQuery (attempt, client, endpoint, options) {
const retry = this.options.retry || { attempts: 1 }
try {
const r = await client(endpoint, options)

Expand Down Expand Up @@ -314,10 +330,16 @@ class Api {
const ClientError = getErrorByCode(r.status)
throw new ClientError(r.statusText, content, r.status)
} catch (e) {
const retry = retrySettings(this.options.retry)
// @ts-ignore
if (retry.attempts && retry.errors && attempt < retry.attempts && retry.errors.includes(e.code)) {
// @ts-ignore
console.warn(`Got ${e.code} when calling ${endpoint}. Retrying request (${attempt}/${retry.attempts})`)
const code = e.code || (e.cause && e.cause.code)
const retriesCount = attempt - 1
const hasAttemptsLeft = retriesCount < retry.attempts
const isRetryableError = retry.errors.includes(code)
const isRetryableMethod = retry.methods.includes(options.method)

if (hasAttemptsLeft && isRetryableError && isRetryableMethod) {
console.warn(`Got ${code} when calling ${endpoint}. Retrying request (${attempt}/${retry.attempts})`)
return this.#doQuery(++attempt, client, endpoint, options)
}

Expand Down
Loading
Loading