node.jsaxiosadapteraxios-retry

How can I change the result status in Axios with an adapter?


The why

We're using the axios-retry library, which uses this code internally:

axios.interceptors.response.use(null, error => {

Since it only specifies the error callback, the Axios documentation says:

Any status codes that falls outside the range of 2xx cause this function to trigger

Unfortunately we're calling a non-RESTful API that can return 200 with an error code in the body, and we need to retry that.

We've tried adding an Axios interceptor before axios-retry does and changing the result status in this case; that did not trigger the subsequent interceptor error callback though.

What did work was specifying a custom adapter. However this is not well-documented and our code does not handle every case.

The code

const axios = require('axios');
const httpAdapter = require('axios/lib/adapters/http');
const settle = require('axios/lib/core/settle');
const axiosRetry = require('axios-retry');

const myAdapter = async function(config) {
  return new Promise((resolve, reject) => {
    // Delegate to default http adapter
    return httpAdapter(config).then(result => {
      // We would have more logic here in the production code
      if (result.status === 200) result.status = 500;
      settle(resolve, reject, result);
      return result;
    });
  });
}

const axios2 = axios.create({
  adapter: myAdapter
});

function isErr(error) {
  console.log('retry checking response', error.response.status);
  return !error.response || (error.response.status === 500);
}

axiosRetry(axios2, {
  retries: 3,
  retryCondition: isErr
});

// httpstat.us can return various status codes for testing
axios2.get('http://httpstat.us/200')
  .then(result => {
    console.log('Result:', result.data);
  })
  .catch(e => console.error('Service returned', e.message));

This works in the error case, printing:

retry checking response 500
retry checking response 500
retry checking response 500
retry checking response 500
Service returned Request failed with status code 500

It works in the success case too (change the URL to http://httpstat.us/201):

Result: { code: 201, description: 'Created' }

The issue

Changing the URL to http://httpstat.us/404, though, results in:

(node:19759) UnhandledPromiseRejectionWarning: Error: Request failed with status code 404
    at createError (.../node_modules/axios/lib/core/createError.js:16:15)
    at settle (.../node_modules/axios/lib/core/settle.js:18:12)

A catch on the httpAdapter call will catch that error, but how do we pass that down the chain?

What is the correct way to implement an Axios adapter?

If there is a better way to handle this (short of forking the axios-retry library), that would be an acceptable answer.

Update

A coworker figured out that doing .catch(e => reject(e)) (or just .catch(reject)) on the httpAdapter call appears to handle the issue. However we'd still like to have a canonical example of implementing an Axios adapter that wraps the default http adapter.


Solution

  • Here's what worked (in node):

    const httpAdapter = require('axios/lib/adapters/http');
    const settle = require('axios/lib/core/settle');
    
    const customAdapter = config =>
      new Promise((resolve, reject) => {
        httpAdapter(config).then(response => {
          if (response.status === 200)
            // && response.data contains particular error
          {
            // log if desired
            response.status = 503;
          }
          settle(resolve, reject, response);
        }).catch(reject);
      });
    
    // Then do axios.create() and pass { adapter: customAdapter }
    // Now set up axios-retry and its retryCondition will be checked