JustinBeckwith/retry-axios

This lib does not work no node_threads

leopucci opened this issue · 12 comments

Hey there, nice work on this lib!

I am having trouble to use on node.js for some reason it does not retry.
I am already using axios interceptors for request to put the Bearer JWT Token on place.
Any hints?

let api = axios.create({
  baseURL: baseURL,
  headers: {
    'Content-Type': 'application/json',
  },
});
const myRaxConfig = {
  // Retry 3 times on requests that return a response (500, etc) before giving up.  Defaults to 3.
  retry: 3,
  // Retry twice on errors that don't return a response (ENOTFOUND, ETIMEDOUT, etc).
  noResponseRetries: 3,
  // Milliseconds to delay at first.  Defaults to 100. Only considered when backoffType is 'static'
  retryDelay: 1000,
  // HTTP methods to automatically retry.  Defaults to:
  // ['GET', 'HEAD', 'OPTIONS', 'DELETE', 'PUT']
  httpMethodsToRetry: ['GET', 'HEAD', 'OPTIONS', 'DELETE', 'PUT'],
  // The response status codes to retry.  Supports a double
  // array with a list of ranges.  Defaults to:
  // [[100, 199], [429, 429], [500, 599]]
  statusCodesToRetry: [
    [100, 199],
    //[429, 429], 429 eh o retorno de too many requests
    [500, 599],
  ],
  // If you are using a non static instance of Axios you need
  // to pass that instance here (const ax = axios.create())
  instance: api,
  // You can set the backoff type.
  // options are 'exponential' (default), 'static' or 'linear'
  backoffType: 'static',
  // You can detect when a retry is happening, and figure out how many
  // retry attempts have been made
  onRetryAttempt: (err) => {
    const cfg = rax.getConfig(err);
    childLogger.error(`myRaxConfig Retry attempt #${cfg.currentRetryAttempt}`);
  },
};
api.defaults.raxConfig = {
  instance: api,
};
const interceptorId = rax.attach(api);

Simple config, nothing fancy.

I am using basico post methods

 api.post('/delete', {
          myvar: myvar,
        })
        .then(function (response) {
}).catch(function (error) {
         console.log(error);
});

So to test I am shuting down the server and getting ECONNREFUSED but there is no retry on them, it´s going directly to .catch. Any hints? Thanks!

Greetings! By default, this module will not retry requests using the POST method. HTTP POST is generally not considered to be idempotent, so it wouldn't be safe to try these multiple times in many cases. If you wanna hit with a hammer and retry though, you can pass POST into the list of accepted values in httpMethodsToRetry:

import * as rax from 'retry-axios';
import axios from 'axios';

const api = axios.create({
  baseURL: 'http://localhost:3000',
  headers: {
    'Content-Type': 'application/json',
  },
});
api.defaults.raxConfig = {
  instance: api,
  noResponseRetries: 3,
  httpMethodsToRetry: ['POST'],
  onRetryAttempt: err => {
    const cfg = rax.getConfig(err);
    console.log(`Retry attempt #${cfg.currentRetryAttempt}`);
  },
};
rax.attach(api);
api.post('/delete', {
  myvar: 12345,
}).then(function (response) {
  console.log(response);
}).catch(function (error) {
  console.log(error);
});

Best of luck!

Thank you @JustinBeckwith indeed I forgot to put this option on the github issue but definitely is not it.
I also tested a simple get to a unknown address and there is no retry on that either
What is happening is that the config that i set is not the same config on the error
Below is the .get, the return and then the config that was set. 404 is inside [400,499] but on the error response there is no [400,499]

api
  .get('asdas.asdasd.ooo')
  .then((res) => {
    console.log('STRANGE');
  })
  .catch((err) => {
console.log('Error testing'+ safeJsonStringfy(err));
  });
Error testing {
  "message": "Request failed with status code 404",
  "name": "Error",
  "stack": "Error: Request failed with status code 404\n    at createError (C:\\pkt\\node_modules\\axios\\lib\\core\\createError.js:16:15)\n    at settle (C:\\pkt\\node_modules\\axios\\lib\\core\\settle.js:17:12)\n    at IncomingMessage.handleStreamEnd (C:\\pkt\\node_modules\\axios\\lib\\adapters\\http.js:322:11)\n    at IncomingMessage.emit (node:events:406:35)\n    at IncomingMessage.emit (node:domain:470:12)\n    at endReadableNT (node:internal/streams/readable:1331:12)\n    at processTicksAndRejections (node:internal/process/task_queues:83:21)",
  "config": {
    "transitional": {
      "silentJSONParsing": true,
      "forcedJSONParsing": true,
      "clarifyTimeoutError": false
    },
    "transformRequest": [
      null
    ],
    "transformResponse": [
      null
    ],
    "timeout": 0,
    "xsrfCookieName": "XSRF-TOKEN",
    "xsrfHeaderName": "X-XSRF-TOKEN",
    "maxContentLength": -1,
    "maxBodyLength": -1,
    "headers": {
      "Accept": "application/json, text/plain, */*",
      "Content-Type": "application/json",
    },
    "baseURL": "http://localhost:2000",
    "raxConfig": {
      "currentRetryAttempt": 0,
      "retry": 3,
      "retryDelay": 100,
      "backoffType": "exponential",
      "httpMethodsToRetry": [
        "GET",
        "HEAD",
        "PUT",
        "OPTIONS",
        "DELETE"
      ],
      "noResponseRetries": 2,
      "checkRetryAfter": true,
      "maxRetryAfter": 300000,
      "statusCodesToRetry": [
        [
          100,
          199
        ],
        [
          429,
          429
        ],
        [
          500,
          599
        ]
      ]
    },
    "method": "get",
    "url": "asdas.storage.ooo"
  },
  "status": 404
}
const myRaxConfig = {
  // Retry 3 times on requests that return a response (500, etc) before giving up.  Defaults to 3.
  retry: 3,
  // Retry twice on errors that don't return a response (ENOTFOUND, ETIMEDOUT, etc).
  noResponseRetries: 3,
  // Milliseconds to delay at first.  Defaults to 100. Only considered when backoffType is 'static'
  retryDelay: 1000,
  // HTTP methods to automatically retry.  Defaults to:
  // ['GET', 'HEAD', 'OPTIONS', 'DELETE', 'PUT']
  httpMethodsToRetry: ['GET', 'HEAD', 'OPTIONS', 'DELETE', 'PUT', 'POST'],
  // The response status codes to retry.  Supports a double
  // array with a list of ranges.  Defaults to:
  // [[100, 199], [429, 429], [500, 599]]
  statusCodesToRetry: [
    [100, 199],
    [400, 499],
    //[429, 429], 429 eh o retorno de too many requests
    [500, 599],
  ],
  // If you are using a non static instance of Axios you need
  // to pass that instance here (const ax = axios.create())
  instance: api,
  // You can set the backoff type.
  // options are 'exponential' (default), 'static' or 'linear'
  backoffType: 'static',
  // You can detect when a retry is happening, and figure out how many
  // retry attempts have been made
  onRetryAttempt: (err) => {
    const cfg = rax.getConfig(err);
    childLogger.error(`myRaxConfig Retry attempt #${cfg.currentRetryAttempt}`);
  },
};
api.defaults.raxConfig = {
  instance: api,
};
const interceptorId = rax.attach(api);

Are you 100% sure about the options you're passing in? A small, very specific code sample that shows what y ou're seeing would be super helpful. I'm specifically poking at this:

"statusCodesToRetry": [
        [
          100,
          199
        ],
        [
          429,
          429
        ],
        [
          500,
          599
        ]
      ]

That makes it look like maybe you're not including 404s in the status codes to retry ...

Hi @JustinBeckwith I know it sounds strange, it´s real it´s not working.
I´m using this on electron, but on a node thread not electron thread.

This is part of my code.
This is what I did
My config just got ignored and the error contain some default config I guess

const myRaxConfig = {
  // Retry 3 times on requests that return a response (500, etc) before giving up.  Defaults to 3.
  retry: 3,
  // Retry twice on errors that don't return a response (ENOTFOUND, ETIMEDOUT, etc).
  noResponseRetries: 3,
  // Milliseconds to delay at first.  Defaults to 100. Only considered when backoffType is 'static'
  retryDelay: 1000,
  // HTTP methods to automatically retry.  Defaults to:
  // ['GET', 'HEAD', 'OPTIONS', 'DELETE', 'PUT']
  httpMethodsToRetry: ['GET', 'HEAD', 'OPTIONS', 'DELETE', 'PUT', 'POST'],
  // The response status codes to retry.  Supports a double
  // array with a list of ranges.  Defaults to:
  // [[100, 199], [429, 429], [500, 599]]
  statusCodesToRetry: [
    [100, 199],
    [400, 499],
    //[429, 429], 429 eh o retorno de too many requests
    [500, 599],
  ],
  // If you are using a non static instance of Axios you need
  // to pass that instance here (const ax = axios.create())
  instance: api,
  // You can set the backoff type.
  // options are 'exponential' (default), 'static' or 'linear'
  backoffType: 'static',
  // You can detect when a retry is happening, and figure out how many
  // retry attempts have been made
  onRetryAttempt: (err) => {
    const cfg = rax.getConfig(err);
    childLogger.error(`myRaxConfig Retry attempt #${cfg.currentRetryAttempt}`);
  },
};
api.defaults.raxConfig = {
  instance: api,
};
const interceptorId = rax.attach(api);

And this is the log of the error from axios


Error testing {
  "message": "Request failed with status code 404",
  "name": "Error",
  "stack": "Error: Request failed with status code 404\n    at createError (C:\\pkt\\node_modules\\axios\\lib\\core\\createError.js:16:15)\n    at settle (C:\\pkt\\node_modules\\axios\\lib\\core\\settle.js:17:12)\n    at IncomingMessage.handleStreamEnd (C:\\pkt\\node_modules\\axios\\lib\\adapters\\http.js:322:11)\n    at IncomingMessage.emit (node:events:406:35)\n    at IncomingMessage.emit (node:domain:470:12)\n    at endReadableNT (node:internal/streams/readable:1331:12)\n    at processTicksAndRejections (node:internal/process/task_queues:83:21)",
  "config": {
    "transitional": {
      "silentJSONParsing": true,
      "forcedJSONParsing": true,
      "clarifyTimeoutError": false
    },
    "transformRequest": [
      null
    ],
    "transformResponse": [
      null
    ],
    "timeout": 0,
    "xsrfCookieName": "XSRF-TOKEN",
    "xsrfHeaderName": "X-XSRF-TOKEN",
    "maxContentLength": -1,
    "maxBodyLength": -1,
    "headers": {
      "Accept": "application/json, text/plain, */*",
      "Content-Type": "application/json",
    },
    "baseURL": "http://localhost:2000",
    "raxConfig": {
      "currentRetryAttempt": 0,
      "retry": 3,
      "retryDelay": 100,
      "backoffType": "exponential",
      "httpMethodsToRetry": [
        "GET",
        "HEAD",
        "PUT",
        "OPTIONS",
        "DELETE"
      ],
      "noResponseRetries": 2,
      "checkRetryAfter": true,
      "maxRetryAfter": 300000,
      "statusCodesToRetry": [
        [
          100,
          199
        ],
        [
          429,
          429
        ],
        [
          500,
          599
        ]
      ]
    },
    "method": "get",
    "url": "asdas.storage.ooo"
  },
  "status": 404
}

@JustinBeckwith aside from that, could you clarify another thing?
If you wanna hit with a hammer and retry though, you can pass POST into the list of accepted values in httpMethodsToRetry:
Why do you consider POST a hammer? what changes on the http request that makes post worst that get?
I´m telling you because I changed some of my requests to post on the server side and didn´t considered something harmful. Where is the catch?

Sure! So an HTTP POST could result in changes being made to the state of the data on the server. When you get something like a 500 response code from a web server, that really makes it unclear to any client if the actual changes desired by the request have been reflected on the backend. As a result, retrying a POST request is considered dangerous - after the second POST, you can't confirm the state of the data on the backend (in a general sense). This is kind of unique to a POST:

  • GET doesn't change the state of the backend
  • PUT inserts an item at a specific URL so the worst that can happen is a retry tells you the resource already exists
  • DELETE same as put, worse that happens on a retry is that it already has been deleted

... and so on. If you specifically know exactly what your backend will do, and decide that a POST is safe to retry ... go for it!

Thanks @JustinBeckwith it´s clear to me now. If you have some hint on the node thread running axios that does not retry, this also happens with other modules, somehow axios is diferent on node/electron.. but I manage to use it on both threads, electron and nodejs thread... but I can´t use plugins somehow.

Thanks man

https://stackoverflow.com/a/64650151/3156756

Seems that is not only me that is having trouble to use this lib.
const res = await myAxiosInstance.get('https://test.local', { raxConfig } );

Do I need to attach this raxConfig with the request even if I did attached everything?

This lib does not work on node threads. for some reason it does not retry on any way.

Seems to work with get... does not work with posts

Interceptor Response shows that my configuration is being overrided with default config

"raxConfig": {
     "currentRetryAttempt": 0,
     "retry": 3,
     "retryDelay": 100,
     "backoffType": "exponential",
     "httpMethodsToRetry": [
       "GET",
       "HEAD",
       "PUT",
       "OPTIONS",
       "DELETE"
     ],
     "noResponseRetries": 2,
     "checkRetryAfter": true,
     "maxRetryAfter": 300000,
     "statusCodesToRetry": [
       [
         100,
         199
       ],
       [
         429,
         429
       ],
       [
         500,
         599
       ]
     ]
   },

Solved with some tweaks

const myAxiosInstance = axios.create();
## THIS IS THE GUILTY ONE START##
myAxiosInstance.defaults.raxConfig = {
  instance: myAxiosInstance
};
## THIS IS THE GUILTY ONE END##
const interceptorId = rax.attach(myAxiosInstance);
const res = await myAxiosInstance.get('https://test.local');