{"id":17962767,"url":"https://github.com/fgribreau/node-request-retry","last_synced_at":"2025-05-15T09:04:22.709Z","repository":{"id":15308950,"uuid":"18038842","full_name":"FGRibreau/node-request-retry","owner":"FGRibreau","description":":guardsman: Wrap NodeJS request module to retry http requests in case of errors","archived":false,"fork":false,"pushed_at":"2023-10-12T21:21:27.000Z","size":451,"stargazers_count":349,"open_issues_count":10,"forks_count":77,"subscribers_count":12,"default_branch":"master","last_synced_at":"2024-10-29T12:11:49.437Z","etag":null,"topics":["http-client","http-requests","requests"],"latest_commit_sha":null,"homepage":"http://twitter.com/FGRibreau","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/FGRibreau.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"fgribreau"}},"created_at":"2014-03-23T17:23:32.000Z","updated_at":"2024-01-11T11:24:14.000Z","dependencies_parsed_at":"2024-06-18T12:11:13.072Z","dependency_job_id":"0644dd1e-8707-4b72-961b-5c5d009e936b","html_url":"https://github.com/FGRibreau/node-request-retry","commit_stats":{"total_commits":241,"total_committers":38,"mean_commits":6.342105263157895,"dds":"0.34024896265560167","last_synced_commit":"db41fa1c157d13299b53865ad206dfa256937433"},"previous_names":[],"tags_count":42,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FGRibreau%2Fnode-request-retry","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FGRibreau%2Fnode-request-retry/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FGRibreau%2Fnode-request-retry/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FGRibreau%2Fnode-request-retry/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/FGRibreau","download_url":"https://codeload.github.com/FGRibreau/node-request-retry/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248890582,"owners_count":21178466,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["http-client","http-requests","requests"],"created_at":"2024-10-29T11:20:37.126Z","updated_at":"2025-04-14T13:46:26.266Z","avatar_url":"https://github.com/FGRibreau.png","language":"JavaScript","readme":"\u003cdiv align=\"center\"\u003e\n  \u003cbr\u003e\u003cp\u003e\u003cstrong\u003erequest-retry\u003c/strong\u003e - HTTP(s) request retry on recoverable errors.\u003c/p\u003e\n\u003c/div\u003e\n\n------------------------------------------------\n\n[![Coverage Status](https://img.shields.io/coveralls/FGRibreau/node-request-retry/master.svg)](https://coveralls.io/github/FGRibreau/node-request-retry?branch=master) [![NPM version](https://img.shields.io/npm/v/requestretry.svg)](http://badge.fury.io/js/requestretry) [![Downloads](http://img.shields.io/npm/dm/requestretry.svg)](https://www.npmjs.com/package/requestretry)\n\n[![Get help on Codementor](https://cdn.codementor.io/badges/get_help_github.svg)](https://www.codementor.io/francois-guillaume-ribreau?utm_source=github\u0026utm_medium=button\u0026utm_term=francois-guillaume-ribreau\u0026utm_campaign=github)  [![available-for-advisory](https://img.shields.io/badge/available%20for%20consulting%20advisory-yes-ff69b4.svg?)](http://bit.ly/2c7uFJq) ![extra](https://img.shields.io/badge/actively%20maintained-yes-ff69b4.svg) [![Slack](https://img.shields.io/badge/Slack-Join%20our%20tech%20community-17202A?logo=slack)](https://join.slack.com/t/fgribreau/shared_invite/zt-edpjwt2t-Zh39mDUMNQ0QOr9qOj~jrg)\n\nWhen the connection fails with one of `ECONNRESET`, `ENOTFOUND`, `ESOCKETTIMEDOUT`, `ETIMEDOUT`, `ECONNREFUSED`, `EHOSTUNREACH`, `EPIPE`, `EAI_AGAIN` or when an HTTP 5xx or 429 error occurrs, the request will automatically be re-attempted as these are often recoverable errors and will go away on retry.\n\n\n\u003e ## ❤️ Shameless plug\n\u003e - Need to implement Webhooks inside your SaaS? [Try Hook0, an open-source self-hostable webhook micro-service](https://www.hook0.com/)\n\u003e - [**Charts, simple as a URL**. No more server-side rendering pain, 1 url = 1 chart](https://image-charts.com)\n\u003e - [Managed **Keycloak IAM** ? Try Cloud-IAM](https://www.cloud-iam.com/)\n\u003e - [Automate your second brain on RoamResearch with Zapier](https://roam-bot.com/)\n\n## Installation\n\nInstall with [npm](https://npmjs.org/package/requestretry).\n\n    npm install --save requestretry\n\n## Usage\n\nRequest-retry is a drop-in replacement for [request](https://github.com/mikeal/request) but adds three new options `maxAttempts`, `retryDelay` and `retryStrategy`. It also adds one property to the response (or the error object, upon a network error), `attempts`. It supports callbacks or promises.\n\n### With callbacks\n\n```javascript\nvar request = require('requestretry');\n\nrequest({\n  url: 'https://api.domain.com/v1/a/b',\n  json: true,\n\n  // The below parameters are specific to request-retry\n  maxAttempts: 5,   // (default) try 5 times\n  retryDelay: 5000,  // (default) wait for 5s before trying again\n  retryStrategy: request.RetryStrategies.HTTPOrNetworkError // (default) retry on 5xx or network errors\n}, function(err, response, body){\n  // this callback will only be called when the request succeeded or after maxAttempts or on error\n  if (response) {\n    console.log('The number of request attempts: ' + response.attempts);\n  }\n});\n```\n\n### With promises\n\nWhen you're using promises, you can pass the two following options:\n- `fullResponse` _(default true)_ - To resolve the promise with the full response or just the body\n- `promiseFactory` _(default whenjs)_ - A function to allow the usage of a different promise implementation library\n\n```javascript\nrequest({\n  url: 'https://api.domain.com/v1/a/b',\n  json: true,\n\n  fullResponse: true // (default) To resolve the promise with the full response or just the body\n})\n.then(function (response) {\n  // response = The full response object or just the body\n})\n.catch(function(error) {\n  // error = Any occurred error\n})\n```\n\n**Using `promiseFactory` option to use a different promise implementation library**\n\n```javascript\n// See the tests for different libraries usage examples\n\n/**\n * @param  {Function} resolver The promise resolver function\n * @return {Object} The promise instance\n */\nfunction customPromiseFactory(resolver) {\n  // With when.js\n  return require('when').promise(resolver);\n\n  // With RSVP.js\n  var Promise = require('rsvp').Promise;\n\n  return new Promise(resolver);\n}\n\nrequest({\n  url: 'https://api.domain.com/v1/a/b',\n  json: true,\n\n  // Custom promise factory function\n  promiseFactory: customPromiseFactory\n})\n.then(function (response) {\n  // response = The full response object or just the body\n})\n.catch(function(error) {\n  // error = Any occurred error\n})\n```\n\n## How to define your own retry strategy\n\nA retry strategy let you specify when request-retry should retry a request\n\n```javascript\n/**\n * @param  {Null | Object} err\n * @param  {Object} response\n * @param  {Object} body\n * @param  {Object} options copy \n * @return {Boolean} true if the request should be retried\n */\nfunction myRetryStrategy(err, response, body, options){\n  // retry the request if we had an error or if the response was a 'Bad Gateway'\n  return !!err || response.statusCode === 502;\n}\n\n/**\n * @param  {Null | Object} err\n * @param  {Object} response\n * @param  {Object} body\n * @param  {Object} options copy \n * @return {Object} mustRetry: {Boolean} true if the request should be retried\n *                  options: {Object} new options for request\n */\nfunction myRetryStrategy(err, response, body, options){\n  options.url = 'new url'; //you can overwrite some attributes or create new object \n  return {\n    mustRetry: !!err || response.statusCode === 502,\n    options: options, //then it should be passed back, it will be used for new requests\n  }\n}\n\n/**\n * With an asynchronous retry strategy\n * @param  {Null | Object} err\n * @param  {Object} response\n * @param  {Object} body\n * @param  {Object} options copy \n * @return {Object} mustRetry: {Boolean} true if the request should be retried\n *                  options: {Object} new options for request\n */\nasync function myRetryStrategy(err, response, body, options){\n  let token = await getNewApiAuthToken();\n  options.headers = {'Authorization': `Bearer ${token}`}\n  return {\n    mustRetry: true,\n    options: options, // retry with new auth token\n  }\n}\n\nrequest({\n  url: 'https://api.domain.com/v1/a/b'\n  json:true,\n  retryStrategy: myRetryStrategy\n}, function(err, response, body){\n  // this callback will only be called when the request succeeded or after maxAttempts or on error\n});\n```\n\n## How to define your own delay strategy\n\nA delay strategy let you specify how long request-retry should wait before trying again the request\n\n```javascript\n/**\n * @param  {Null | Object} err\n * @param  {Object} response\n * @param  {Object} body\n * @return {Number} number of milliseconds to wait before trying again the request\n */\nfunction myDelayStrategy(err, response, body){\n  // set delay of retry to a random number between 500 and 3500 ms\n  return Math.floor(Math.random() * (3500 - 500 + 1) + 500);\n}\n\nrequest({\n  url: 'https://api.domain.com/v1/a/b'\n  json:true,\n  delayStrategy: myDelayStrategy // delayStrategy is called 1 less times than the maxAttempts set\n}, function(err, response, body){\n  // this callback will only be called when the request succeeded or after maxAttempts or on error\n});\n```\n\nHere is how to implement an exponential backoff strategy:\n\n```javascript\n/**\n * @param   {Number} attempts The number of times that the request has been attempted.\n * @return  {Number} number of milliseconds to wait before retrying again the request.\n */\nfunction getExponentialBackoff(attempts) {\n  return (Math.pow(2, attempts) * 100) + Math.floor(Math.random() * 50);\n}\n\nfunction constructExponentialBackoffStrategy() {\n  let attempts = 0;\n  return () =\u003e {\n    attempts += 1;\n    return getExponentialBackoff(attempts);\n  };\n}\n\nrequest({\n  url: 'https://api.domain.com/v1/a/b'\n  json:true,\n  delayStrategy: constructExponentialBackoffStrategy() // need to invoke the function to return the closure.\n}, function(err, response, body){\n  // this callback will only be called when the request succeeded or after maxAttempts or on error\n});\n```\n\n## How to access the underlying request library\n\nYou can access to the underlying `request` library thanks to `request.Request`:\n\n```javascript\nconst request = require('requestretry');\nconsole.log(request.Request); // original request library\n```\n\nThus, if needed, it's possible to monkey-patch or extend the underlying Request library:\n\n```javascript\nrequest.Request = class extends request.Request {\n  constructor(url, options, f, retryConfig) {\n    super(url, options, f, retryConfig);\n    // this constructor will be called for every requestretry call,\n    // and give you global logging\n    console.log('Request', url, options, f, retryConfig);\n  }\n}\n```\n\n\n## Modifying `request` options\n\nYou can use the `defaults` method to provide default options like so:\n\n```javascript\nvar request = require('requestretry').defaults({ json: true, retryStrategy: myRetryStrategy });\n```\n\n## API surface\n\nAs with `request`, several helpers are provided for various HTTP methods and usage:\n\n* `request(options [, callback])`.\n* `request(url [, callback])` - same as `request(options [, callback])`.\n* `request(url, options [, callback])` - same as `request(options [, callback])`.\n* `request.get(url [, callback])` - same as `request(options [, callback])`, defaults `options.method` to `GET`.\n* `request.get(url, options  [, callback])` - same as `request(options [, callback])`, defaults `options.method` to `GET`.\n* `request.head(url)` - same as `request(options [, callback])`, defaults `options.method` to `HEAD`.\n* `request.post(url)` - same as `request(options [, callback])`, defaults `options.method` to `POST`.\n* `request.put(url)` - same as `request(options [, callback])`, defaults `options.method` to `PUT`.\n* `request.patch(url)` - same as `request(options [, callback])`, defaults `options.method` to `PATCH`.\n* `request.del(url)` - same as `request(options [, callback])`, defaults `options.method` to `DELETE`.\n* `request.delete(url)` - same as `request(options [, callback])`, defaults `options.method` to `DELETE`.\n\n## [Changelog](CHANGELOG.md)\n\n## You want to support my work?\n\nI maintain this project in my free time, if it helped you, well, I would be grateful to buy a beer thanks to your [paypal](https://paypal.me/fgribreau) or [Bitcoins](https://www.coinbase.com/fgribreau), donation!\n\n[Francois-Guillaume Ribreau](http://fgribreau.com) (npm@fgribreau.com)\n","funding_links":["https://github.com/sponsors/fgribreau","https://paypal.me/fgribreau"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffgribreau%2Fnode-request-retry","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffgribreau%2Fnode-request-retry","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffgribreau%2Fnode-request-retry/lists"}