background

Why add interceptors to fetch? Is the fetch method provided by the browser not enough?

Yes. Native FETCH is really not enough.

In a project, if you want to add permission information (for example, authorization= XXXX) to the header for all network requests. With native FETCH, you can only write authorization= XXXX in the header configuration for each fetch request. Or do some special processing to the response result after the request result is returned. In native FETCH, we can only write the special processing after each request result is returned. There’s nothing elegant about it. There’s nothing advanced about it. Believe that every programmer is a lazy person, can use less code to implement a more elegant, more robust program, never need a lot of code to implement a very fragile program.

Anyone who has used Axios knows that Axios has axios.interceptors. When something needs to be done before or after all ajax requests are completed, we can write it in the interceptor. An interceptor can intercept a request before it is initiated, do some processing with the request, and then continue the request. You can also intercept the request after it completes, do some processing with the response, and then return the result.

The advantage of this is that we don’t need to write the same code for every request. We can do this by putting global/generic processing in the interceptor. It saves a lot of code, and also prevents us from accidentally missing a request to do global processing during development.

Interceptor design considerations

  1. The goal is to add the ability to intercept requests on top of native FETCH.
  2. After encapsulation, the final exposed interface should be used in the same way as the native FETCH, with the addition of interceptor capabilities
  3. The interceptor API design references axios’s interceptor (personally, I prefer Axios’s interceptor design).

Interceptor API usage methods

You can intercept requests and responses before then and catch.

// Add a request interceptor
c_fetch.interceptors.request.use(function (config) {
    // Do something before request is sent
    return config;
  });

// Add a response interceptor
c_fetch.interceptors.response.use(function (response) {
    // Do something with response data
    return response;
  });
Copy the code

Interceptor implementation scheme

  1. To ensure that a wrapped FETCH exposes the outermost API in the same way as a native FETCH. So, the API we’re exposing to the outside should be a function that takes the same arguments as fetch. Let’s take a look at the native FETCH method syntax explanation and the receive parameter explanation.
Syntax: Promise<Response> fetch(input[, init]);Copy the code
Parameters:? Input defines the resource to fetch. This could be: a USVString string containing the URL of the resource to fetch. Some browsers accept blob: and data: as schemes. A Request object. Init An optional configuration item object that contains all Settings for the request. The options are as follows: Method: indicates the request method, such as GET and POST. Headers: Header information for a request, either a headers object or a literal object containing a ByteString value. Body: The requested body information: could be a Blob, BufferSource, FormData, URLSearchParams, or USVString object. Note that requests for the GET or HEAD methods cannot contain body information. Mode: indicates the request mode, such as CORS, no-cors, or same-origin. Credentials: Required credentials, such as omit, same-origin, or include. This option must be provided in order to automatically send cookies within the current domain name, and as of Chrome 50, this property can also accept either a FederatedCredential instance or a PasswordCredential instance. Cache: Request cache mode: default, no-store, reload, no-cache, force-cache, or only-if-cached. Redirect: Available redirect modes: Follow (automatic redirection), ERROR (automatically terminating and throwing an error if a redirect occurs), or manual (handling redirection manually). In Chrome, the default before Chrome 47 is follow, and starting with Chrome 47 is manual. Referrer: A USVString can be a no-referrer, client, or a URL. The default is Client. referrerPolicy: Specifies the value of the referer HTTP header. May be one of no-referrer, no-referreer-when - reel-must, origin, Origin - the when - cross - origin, the unsafe - url. Integrity: including request of subresource integrity value (for example: sha256 - BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE =).Copy the code

Detailed documentation of native FETCH can be found at MDN: developer.mozilla.org/zh-CN/docs/…

Fetch is a function. The first parameter defines the resource to fetch, and the second, optional, is a configuration item object that contains all Settings for the request. The fetch returns a Promise object.

  1. Let’s first implement the c_FETCH function exposed after encapsulation.
  function c_fetch (input, init = {}) {
    //fetch the default request mode is set to GET
    if(! init.method){ init.method ='GET'
    }
    
    //interceptors_req is a collection of intercepting request handlers
    // The definition of interceptors_req will be provided later
    interceptors_req.forEach(interceptors= > {
      init = interceptors(init);
    })
    
    // Encapsulate a promise around the native FETCH so that the result of the fetch request can be intercepted within the promise.
    // Also, make sure that c_fetch returns a Promise object.
    return new Promise(function (resolve, reject) {
      // Initiate a fetch request. The fetch request takes the parameters to receive the upper function
      fetch(input, init).then(res= > {
        //interceptors_res is a collection of intercepting handlers that intercept response results
        // The definition and implementation of interceptors_res will be provided later
        interceptors_res.forEach(interceptors= > {
          // The interceptor processes the response and returns the processed result to the response.
          res = interceptors(res);
        })
        // Resolve the result of interceptor processing
        resolve(res)
      }).catch(err= >{ reject(err); })})}Copy the code
  1. C_fetch is done, we just need the implementation of the interceptor. We add interceptors to c_fetch to register interceptors.
  // Define a collection of handlers used to store the results of intercepting requests and responses
  let interceptors_req = [], interceptors_res = [];

  function c_fetch (input, init = {}) {
    //fetch the default request mode is set to GET
    if(! init.method){ init.method ='GET'
    }
    
    //interceptors_req is a collection of intercepting request handlers
    interceptors_req.forEach(interceptors= > {
      init = interceptors(init);
    })
    
    // Encapsulate a promise around the native FETCH so that the result of the fetch request can be intercepted within the promise.
    // Also, make sure that c_fetch returns a Promise object.
    return new Promise(function (resolve, reject) {
      // Initiate a fetch request. The fetch request takes the parameters to receive the upper function
      fetch(input, init).then(res= > {
        //interceptors_res is a collection of intercepting handlers that intercept response results
        interceptors_res.forEach(interceptors= > {
          // The interceptor processes the response and returns the processed result to the response.
          res = interceptors(res);
        })
        // Resolve the result of interceptor processing
        resolve(res)
      }).catch(err= >{ reject(err); })})}// Add interceptors to c_fetch. Interceptors provide request and response interceptors.
  // We can bind the two interceptor handlers using the request and response use methods.
  // The use method takes a callback function, which is used as the interceptor's handler;
  // The request.use method puts the callback in the interceptors_req, waiting for execution.
  The response.use method puts callback in the interceptors_res, waiting to be executed.
  // The interceptor's handler callback receives a single argument.
  // The request interceptor callback receives the config before the request is initiated;
  // Response The interceptor's callback receives a response from a network request.
  c_fetch.interceptors = {
    request: {
      use: function (callback) { interceptors_req.push(callback); }},response: {
      use: function (callback) { interceptors_res.push(callback); }}}Copy the code
  1. Finally, after wrapping the whole package, fetch with interceptor functionality is wrapped as a plug-in and exposed to developers.
/** * c_fetch * encapsulates the interceptor function based on the native FETCH. The exposed c_fetch uses the same method as the native FETCH, but adds the interceptor function. Refer to axios' interceptor usage. * Interceptor: c_fetch. Interceptors * Note: The interceptor does not intercept a response of type reject */

  // Define a collection of handlers used to store the results of intercepting requests and responses
  let interceptors_req = [], interceptors_res = [];

  function c_fetch (input, init = {}) {
    //fetch the default request mode is set to GET
    if(! init.method){ init.method ='GET'
    }
    
    //interceptors_req is a collection of intercepting request handlers
    interceptors_req.forEach(interceptors= > {
      init = interceptors(init);
    })
    
    // Encapsulate a promise around the native FETCH so that the result of the fetch request can be intercepted within the promise.
    // Also, make sure that c_fetch returns a Promise object.
    return new Promise(function (resolve, reject) {
      // Initiate a fetch request. The fetch request takes the parameters to receive the upper function
      fetch(input, init).then(res= > {
        //interceptors_res is a collection of intercepting handlers that intercept response results
        interceptors_res.forEach(interceptors= > {
          // The interceptor processes the response and returns the processed result to the response.
          res = interceptors(res);
        })
        // Resolve the result of interceptor processing
        resolve(res)
      }).catch(err= >{ reject(err); })})}// Add interceptors to c_fetch. Interceptors provide request and response interceptors.
  // We can bind the two interceptor handlers using the request and response use methods.
  // The use method takes a callback function, which is used as the interceptor's handler;
  // The request.use method puts the callback in the interceptors_req, waiting for execution.
  The response.use method puts callback in the interceptors_res, waiting to be executed.
  // The interceptor's handler callback receives a single argument.
  // The request interceptor callback receives the config before the request is initiated;
  // Response The interceptor's callback receives a response from a network request.
  c_fetch.interceptors = {
    request: {
      use: function (callback) { interceptors_req.push(callback); }},response: {
      use: function (callback) { interceptors_res.push(callback); }}}export default c_fetch;
Copy the code

conclusion

This article has only implemented a basic interceptor function, and the limited number of words does not go into more mature interceptor implementations. If you are interested, you can read the implementation of Axios’s interceptor, which is very interesting. – Axios interceptors will be improved.

The fetch wrapper I’ve presented here is just the most basic wrapper to explain the implementation of the interceptor without being overly complicated. The c_FETCH exposed above can actually encapsulate a layer of CC_FETCH. Bind the c_FETCH method to cc_FETCH, and finally expose cc_FETCH. This has the advantage of protecting the c_fetch method from external influences, tampering, etc. Of course, this is just my personal opinion, not everyone’s.

Finally, thank you for your persistence in reading to the end, and I hope you can gain from reading this article. Thank you 🙏 ~