The native FETCH does not come with interceptors like Axios. The company recently used the WHATWG-FETCH library in a project, so I thought I’d implement a manual implementation to encapsulate this functionality

Why use interceptors?

In our business, we often need to process requests globally, such as adding API prefix, adding authentication token data, and processing request and response data globally. It is impossible for us to process each request individually for each request. Such workload does not matter when more than ten interfaces are called. If a large project, dozens of hundreds of interfaces, one by one to modify it? And once the requirements change, it will bring a fatal blow, accidentally bugs out. This makes it critical to use interceptors to process requests.

What is an interceptor?

According to the business requirement mentioned in the previous article, we can analyze that interceptor is an intermediate process that calls to process data before request and response and then returns it. In fact, interceptor is the same concept as middleware that we often mention.

  • For example

Add we need to add token data to the header, then add a special identifier to the header, and finally let the browser use the header to access the backend interface.

let options = {
    method: 'GET',
    headers: {
      "content-type": "application/x-www-form-urlencoded"} / /... Omit others} // k1 ----------------------------- is used here as a markerlet http = fetch('/user' options);
http.then(s=>{
    return s.json()
}).then(res=>{
    console.log(res)
})
Copy the code

The above is the process of sending an HTTP request in a FETCH. How to add a token in the header as we said?

function resolveOptions(options){
    options.headers.token = 'beerxddrgdfg24556xxxxxx'
    return options
}
function resolveOptions1(options){
    options.headers.a = '2'
    return options
}
Copy the code

If the above two methods are executed at the k1 marker, can we achieve our results? The answer is yes So resolveOptions resolveOptions1 is two interceptors. Usually our interceptors are executed in chain mode, that is, our options need to go through the interceptor intermediate process one by one before entering the fetch for the request. This is for request, and the same thing for response. The general idea of our interceptor is to process the Request object and the Response object before and after the request. There can be multiple interceptors, so you need to maintain a queue for interceptors to execute sequentially, while also taking into account asynchronism. I naturally thought of the Onion ring model of KOA2 middleware, which WAS also analyzed in one of my articles. Portal -Koa Middleware principles, it is highly recommended to read this section first to better understand the code below

Implement the encapsulation of myFetch

We expect to use myFetch in the same way that KOA uses middleware

Vision:let myFetch = new MyFetch()
// Request interceptor
myFetch.useRequest_interceptors(async (request,next)=>{
    request.a = 1
    await next()
})
// Response interceptor
myFetch.useResponse_interceptors(async (response,next)=>{
    response.data.b = {a:1.b:2}
    await next()
})
myFetch.fetch('/user', {id:1}).then(res= >{
    console.log(res)
    // The res has already been processed by the response interceptor
})
Copy the code

Implement the myFetch base class

interface InterceptorFn {
  (payload: any, next: Function): any
}
class FetchAPI implements FetchAPIType {
  fetch: (url: string, options: any) = > Promise<any>;
  request_interceptors: Array<InterceptorFn> = [];
  response_interceptors: Array<InterceptorFn> = [];
  constructor() {
    this.fetch = this.fetchMethod
  }
  // Define the Request interceptor
  useRequestInterceptors(fn: InterceptorFn) {
    this.request_interceptors.push(fn);
  }
  // Define response interceptor
  useResponseInterceptors(fn: InterceptorFn) {
    this.response_interceptors.push(fn);
  }
  fetchMethod = (url: string, options: any) = > {
    return fetchPro(
      url,
      options,
      this.request_interceptors,
      this.response_interceptors ); }}Copy the code

Implement fetchMethod

 const fetchPro: fetchPro = (
 url,initOptions = {},req_interceptors,res_interceptors
) => {
  initOptions.url = url;
  initOptions.mode = "cors";
  if (req_interceptors.length > 0) {
  // Combine all request interceptors in this place so that they are executed in sequence
    composeInterceptors(req_interceptors, initOptions)();
  }
  return new Promise((resolve, reject) = > {
    const URL = initOptions.url;
    delete initOptions.url;
    console.log('promise-methods')
    // Initiate a fetch request. The fetch request takes the parameters to receive the upper function
    fetch(URL, initOptions).then((res: any) = > res.json()).then((result: any) = > {
      if (res_interceptors.length > 0) {
       // Combine all response interceptors in this place so that they are executed in sequence
        let fn = composeInterceptors(res_interceptors, result);
        fn()
      }
      // Resolve the result of interceptor processing
      resolve(result);
    })
      .catch((err: any) = > {
        reject(err);
      });
  });
};
Copy the code

Implement the Compose interceptor queue



interface InterceptorFn {
  (payload: any, next: Function): any
}
interface fetchPro {
  (url: string, initOptions: any, req_interceptors: Array<InterceptorFn>, res_interceptors: Array<InterceptorFn>): Promise<any>
}
interface ComposeInterceptors {
  (Interceptor: Array<InterceptorFn>, payload: any): any
}


const createNextInterceptor: Function = (Interceptor: InterceptorFn, next: InterceptorFn, payload: any) = > {
  return async function () {
    return await Interceptor(payload, next);
  };
};
/ / Interceptors
const composeInterceptors: ComposeInterceptors = (Interceptor, payload) = > {
// All interceptors are normalized by Reduce, and the method that executes the next middleware is wrapped up and passed as a parameter to next
  return Interceptor.reduceRight(
    (pre, cur) = > {
      return createNextInterceptor(cur, pre, payload);
    },
    result => {
      Promise.resolve(); }); };Copy the code

conclusion

Since the project uses WHATWG-FETCH, the request and response types are not defined when we use typescript, so we use any type. We go to NPM to check that the types of whatWG-Fetch have been removed. Therefore, the code prompt and verification of request and response in the middleware are not enough, so we haven’t found a solution yet. Further optimization as an optimization point.