This is the fifth day of my participation in the More text Challenge. For details, see more text Challenge

Axios is a common ajax request library in our work, and we as front end engineers wanted to see how Axios built the framework, with the interceptors, adapters, and cancel requests in between.

preface

Because there are a lot of non-essential methods in the Axios source code, many of them are not written in ES6 syntax for compatibility purposes. This article will take you through the main processes of AxiOS and rewrite the simple version of AXIOS with ES6

  • The interceptor
  • The adapter
  • Cancel the request

The interceptor

There are two interceptors on an Axios instance, a request interceptor and then a response interceptor. Let’s look at the use of the website:

Adding interceptors

// Add a request interceptor
axios.interceptors.request.use(function (config) {
    // What to do before sending the request
    return config;
  }, function (error) {
    // What to do about the request error
    return Promise.reject(error);
  });
Copy the code

Remove interceptor

const myInterceptor = axios.interceptors.request.use(function () {/ *... * /});
axios.interceptors.request.eject(myInterceptor);
Copy the code

In fact, the source code is the execution of all interceptors so there must be a forEach method.

Now that we have cleared our minds, let’s start to write. I just send the code out, and I annotate it.

export class InterceptorManager {
  constructor() {
    // Stack to hold all interceptors
    this.handlers = []
  }

  use(fulfilled, rejected) {
    this.handlers.push({
      fulfilled,
      rejected,
    })
    // Return id for cancellation
    return this.handlers.length - 1
  }
  // Cancel an interceptor
  eject(id) {
    if (this.handlers[id]) {
      this.handlers[id] = null}}// Execute all hanlders in the stack
  forEach(fn) {
    this.handlers.forEach((item) = > {
      // This is to filter out canceled interceptors, because canceled interceptors are set to NULL
      if (item) {
        fn(item)
      }
    })
  }
}
Copy the code

We have already implemented the interceptor class. Now we want to implement the Axios class. Let’s take a look at the official documentation, first look at the usage, and then analyze.

axios(config)

// Send a POST request
axios({
  method: 'post'.url: '/user/12345'.data: {
    firstName: 'Fred'.lastName: 'Flintstone'}});Copy the code
axios(url[, config])
// Send the GET request (the default method) axios('/user/12345');Copy the code

The core method of the Axios class is actually the request method. Let’s look at the implementation first

class Axios {
  constructor(config) {
    this.defaults = config
    this.interceptors = {
      request: new InterceptorManager(),
      response: new InterceptorManager(),
    }
  }
  // Send a request
  request(config) {
    // Axios (url[,config])
    if (typeof config == 'string') {
      config = arguments[1] || {}
      config.url = arguments[0]}else {
      config = config || {}
    }

    // Defaults to get requests and turns them to lowercase
    if (config.method) {
      config.method = config.method.toLowerCase()
    } else {
      config.method = 'get'
    }

    // dispatchRequest sends ajax requests
    const chain = [dispatchRequest, undefined]
    // Add the interception functions before the request occurs
    this.interceptors.request.forEach((item) = > {
      chain.unshift(item.fulfilled, item.rejected)
    })
    // Add the fulfilled and reject functions after the request
    this.interceptors.response.forEach((item) = > {
      chain.push(item.fulfilled, item.rejected)
    })

    // Use the promise call chain, pass the parameters layer by layer
    let promise = Promise.resolve(config)

    // Then I go through the chain
    while (chain.length) {
      // This is where the stack continues to be pushed until the end
      promise = promise.then(chain.shift(), chain.shift())
    }
    return promise
  }
}
Copy the code

Here is actually embodies the clever design of AXIos, maintain a stack structure + promise chain call to achieve the interceptor function, some friends may not understand here, I still give you a sketch to simulate the process.

Suppose I have a request interceptor handler and a response interceptor handler

We started with two pieces of data in the stack

That’s fine, because there are interceptors, and if there are interceptors, then we’re going to add data to this stack, and the request interceptor is unshift because it’s called before the request. After adding the request interceptor our stack looks like this

No problem, and then after the request is finished, we want to process the data after the request, so the data intercepted in response is naturally push. The stack structure now looks like this:

Then traversing the stack structure, each time out of the stack is a pair of out, because promise then is a success, a failure. At the end of the walk, return the promise that has been processed, and you can get the final value.

adapter

Adapter: Adapter. I’m not going to implement it here, but I’m going to show you the source code. One thing the Adapter does is simply use different requests for different environments. If the user has a custom adapter, use config.adapter. Otherwise, the default is default.adpter.

 var adapter = config.adapter || defaults.adapter;

 return adapter(config).then() ...
Copy the code

Moving on to what deafults. Adapter do:

function getDefaultAdapter() {
  var adapter;
  if (typeofXMLHttpRequest ! = ='undefined') {
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
  } else if (typeofprocess ! = ='undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // For node use HTTP adapter
    adapter = require('./adapters/http');
  }
  return adapter;
}
Copy the code

It’s a choice: XHR if you’re in a browser environment or Node if you’re in a node environment. Determine whether process exists. From a coding perspective, the design here of the Axios source code is very extensible. It’s kind of like the adaptor pattern in design pattern, because the browser side and the node side don’t really send the same requests, but we don’t really matter, we don’t care about the internal implementation, we use promise packages to do it all externally. So when we use axios to customize adapter, we must return a promise. The method of the OK request is simulated below.

cancleToken

Let me start by asking you a question, how do native browsers do this? There is an abort method. You can cancel the request. The Axios source must also use this to cancel the request. Now browsers actually support fetch requests. Fetch can cancel requests, right? A lot of students say no, but it’s not, right? Fetch and abortController cancel the fetch request. Let’s look at an example:

const controller = new AbortController();
const { signal } = controller;

fetch("http://localhost:8000", { signal }).then(response= > {
    console.log(`Request 1 is complete! `);
}).catch(e= > {
    console.warn(`Fetch 1 error: ${e.message}`);
});
// Wait 2 seconds to abort both requests
setTimeout(() = > controller.abort(), 2000);
Copy the code

But it’s an experimental feature, damn IE. So this time we’ll use the native browser XHR to simply encapsulate the Promise. The code is as follows:

export function dispatchRequest(config) {
  return new Promise((resolve, reject) = > {
    const xhr = new XMLHttpRequest()
    xhr.open(config.method, config.url)
    xhr.onreadystatechange = function () {
      if (xhr.status >= 200 && xhr.status <= 300 && xhr.readyState === 4) {
        resolve(xhr.responseText)
      } else {
        reject('Failed')}}if (config.cancelToken) {
      // Handle cancellation
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if(! xhr) {return
        }
        xhr.abort()
        reject(cancel)
        // Clean up request
        xhr = null
      })
    }
    xhr.send()
  })
}
Copy the code

There’s a lot of processing going on in the Axios source code, but here I’m just doing get processing. My main purpose is to see how Axios cancellations requests. Let’s start with the official usage:

There are two main uses:

Cancel the request using the Cancel token

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function(thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
     // Error handling}}); axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})

// Cancel the request (the message argument is optional)
source.cancel('Operation canceled by the user.');
Copy the code

You can also create a Cancel token by passing an executor function to the CancelToken constructor:

const CancelToken = axios.CancelToken;
let cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // The executor function takes a cancel function as an argumentcancel = c; })});// cancel the request
cancel();
Copy the code

Looking at the official usage and combining the Axios source code: I give the following implementation:

export class cancelToken {
    constructor(exactor) {
        if (typeofexecutor ! = ='function') {
        throw new TypeError('executor must be a function.')}// This actually gives control of the promise to the cancel function
        Redux and React source code also have similar cases
        const resolvePromise;
        this.promise =  new Promise(resolve= > {
            resolvePromise = resolve;
        })
        this.reason = undefined;
        
        const cancel  = (message) = > {
            if(this.reason) {
                return;
            }
            this.reason = 'cancel' + message;
            resolvePromise(this.reason);
        }
        exactor(cancel)
    }

    throwIfRequested() {
        if(this.reason) {
            throw this.reason
        }
    }
    
    // Source is essentially a syntactic sugar wrapped inside
    static source() {
        const cancel;
        const token = new cancelToken(function executor(c) {
            cancel = c;
        });
        return {
            token: token,
            cancel: cancel }; }}Copy the code

As of this point, the general functionality of Axios has been given.

So what I’m going to do is I’m going to test my handwritten Axios.

 <script type="module" >
    import Axios from './axios.js';
    const config = { url:'http://101.132.113.6:3030/api/mock' }
    const axios =  new Axios();
    axios.request(config).then(res= > {
        console.log(res,'0000')
    }).catch(err= > {
        console.log(err)
    })
</script>
Copy the code

Open your browser and see the results:

Ok, then I will test the interceptor’s functionality: Update the code to look like this:

import Axios from './axios.js';
const config = { url:'http://101.132.113.6:3030/api/mock' }
const axios =  new Axios();
// Mount the properties on the AXIos instance
const err = () = > {}
axios.interceptors.request.use((config) = > {
    console.log('I'm requesting interceptor 1')
    config.id = 1;
    return  config
},err )
axios.interceptors.request.use((config) = > {
    config.id = 2
    console.log('I'm requesting interceptor 2')
    return config
},err)
axios.interceptors.response.use((data) = > {
    console.log('I am responding to interceptor 1',data )
    data += 1;
    return data;
},err)
axios.interceptors.response.use((data) = > {
    console.log('I am responding to interceptor 2',data )
    return  data
},err)
axios.request(config).then(res= > {
    // console.log(res,'0000')
    // return res;
}).catch(err= > {
    console.log(err)
})
Copy the code

The result of the Ajax request is resolve(1), so let’s look at the output path:

No problem, I added 1 to the data after the response.

I’m going to look at two ways to cancel a request

// Let cancelFun = undefined; const cancelInstance = new cancelToken((c)=>{ cancelFun = c; }); config.cancelToken = cancelInstance; SetTimeout (()=>{cancelFun(' cancelled successfully ')},50) Const {token, cancel} = cancelToken. config.cancelToken = token; setTimeout(()=>{ cancel() },50)Copy the code

The results are OK, so the axios simple source code is finally done.

reflection

This article just goes through the general flow of the Axios source code, but the axios source code does a lot of internal compatibility such as: configure priorities: it has a mergeConfig method, and it has a data converter. However, this does not affect our overall sorting of the Axios source code, which actually has a createInstance, as to why? I think it is for extensibility, any new features will be added directly to the original axios instance prototype chain, code maintainability is strong, axios. All spread is the new instance to hang, but it is very simple, nothing. Read by yourself if you are interested.

conclusion

All the code for this article is on my Github project, welcome star, if this article is helpful to you, welcome to like + follow! The source code series will continue to be updated. If there are any mistakes in the article, please feel free to thank everyone!

Review past

  • Take you from the beginning to the end of a systematic wanking Redux source code
  • React17 source code interpretation – event system