3

Scenes:

As developers, we are most exposed to CRUD, the joint debugging of various interfaces, but it seems that we will pay less attention to the http request we send. When this request is not fulfilled and a new request is sent, then the current situation How should a request be handled? We know that in order to prevent repeated actions, we can use similar anti-shake and throttling to evade, but today we talk about how to avoid repeated requests from the request level, rather than blocking from the user side. In fact, there are many attempts by developers in this area on the Internet, but some of them are not so clear. For this reason, after investigating some knowledge in this area, combined with the scenes I usually encounter in development. Summarize the two most common scenarios where http requests need to be cancelled.

scene one:

The same request needs to be cancelled. The same request here refers to the same method, the same params, and the same url for the get request, and the same method, the same body, and the same url for the Post request.

Scene two:

The routing address has changed (the previous web page request is meaningless)

Implementation:

First of all, in order to achieve repeated cancellation, we need to do two steps. The first step is to know how to cancel, and the second step is how to determine that the current request is a repeated request.

How to cancel repeated requests:

Regarding how to cancel, I will explain this part from the two aspects of axios and fetch, because the two request methods have different methods of canceling.

In order to facilitate understanding, we use react to demonstrate this process more directly, because we know that the hook function useEffect, the characteristics of useEffect determine that the return function will be performed once before each useEffect is executed to clean up, so here, we put The cancel operation is placed here, you can simulate each time you cancel the previous operation

axios

First of all, I will introduce axios. The front-end er must be vomiting. In one sentence, the essence of axios to achieve cancellation is to use its internal encapsulated cancelToken.

First of all, you must know that the token determines the uniqueness, which is also the identification that determines which request needs to be cancelled. It can be generated by cancelToken.source()

source contains the cancel method, we call it to achieve cancellation

useEffect(() => {
  const cancelToken = axios.CancelToken;
  const source = cancelToken.source();
  setAxiosRes("axios request created");
  getReq(source).then((res) => {
    setAxiosRes(res);
  });
  return () => {
    source.cancel("axios request cancelled");
  };
}, [axiosClick]);
export const instance = axios.create({
  baseURL: "http://localhost:4001",
});
export const getReq = async (source) => {
  try {
    const {
      data
    } = await instance.get("/", {
      cancelToken: source.token,
    });
    return data;
  } catch (err) {
    if (axios.isCancel(err)) {
      return "axios request cancelled";
    }
    return err;
  }
};

It should be noted here that the cancel action itself can be captured by the catch part, and it is also an err. We use the isCancel method it provides to determine whether it is a cancellation operation. This can be used to verify whether our cancellation is successful.

fetch:

Then the situation of fetch is different. The implementation of fetch is to cancel through signal. Its internal AbortController() can cancel all requests that respond to the signal mark. It also uses react to simulate it once. In fact, the essence is still the same, and It can also be caught

export const instance = axios.create({
      useEffect(() => {
        const controller = new AbortController();
        const signal = controller.signal;
        setFetchRes("fetch request created");
        hitApi(signal).then((res) => {
          setFetchRes(res);
        });
        //cleanup function
        return () => {
          controller.abort();
        };
      }, [fetchClick]);

The hitApi function is as follows, that is, put the signal into our fetch, so that we can abort.

 export const hitApi = async (signal) => {
        try {
          const response = await fetch("http://localhost:4001/", {
            signal
          });
          const data = await response.json();
          return data;
        } catch (err) {
          if (err.name === "AbortError") {
            return "Request Aborted ";
          }
          return err;
        }
      }

Here the same,'AbortError' can be caught in catch

Determine whether to repeat

Well, the cancellation function is implemented, and then we must consider how to determine whether the request is repeated. There is no doubt that to determine whether it is repeated, you want a constant level of time complexity to find whether there is a repeated request. Of course, you use Map, so that you can find the repeated request at a speed of O(1), and then decide to cancel it. Moreover, it is conceivable that the whole process needs to add things to the array, and of course the ones that have been cancelled need to be taken out, so we need an addition function and a removal function

const addPending = (config) => {
  const url = [
    config.method,
    config.url,
    qs.stringify(config.params),
    qs.stringify(config.data)
  ].join('&')
  config.cancelToken = config.cancelToken || new axios.CancelToken(cancel => {
    if (!pending.has(url)) { // If the current request does not exist in pending, add it
      pending.set(url, cancel)
    }
  })
}

When assigning a value to config.cancelToken, you should pay attention to whether the current config.cancelToken already has a value

In order to facilitate our tiled parameters, we can use qs to convert Object to string

const removePending = (config) => {
  const url = [
    config.method,
    config.url,
    qs.stringify(config.params),
    qs.stringify(config.data)
  ].join('&')
  if (pending.has(url)) { // If the current request identity exists in pending, you need to cancel the current request and remove it
    const cancel = pending.get(url)
    cancel(url)
    pending.delete(url)
  }
}

axios interceptor

However, in actual projects, we usually have an axios interceptor to uniformly manage our requests, so there are many people here who like to directly add these two methods to the axios interceptor, so that it will be done once and for all.

axios.interceptors.request.use(config => {
  removePending(options) // Check previous requests to cancel before the request starts
  addPending(options) // Add current request to pending
  // other code before request
  return config
}, error => {
  return Promise.reject(error)
})
axios.interceptors.response.use(response => {
  removePending(response) // Remove this request at the end of the request
  return response
}, error => {
  if (axios.isCancel(error)) {
    console.log('repeated request: ' + error.message)
  } else {
    // handle error code
  }
  return Promise.reject(error)
})

Route switching usage

Finally, let’s talk about the second scenario. The situation where the route is switched is relatively simple. Just clear our pending queue directly.

export const clearPending = () => {
  for (const [url, cancel] of pending) {
    cancel(url)
  }
  pending.clear()
}
router.beforeEach((to, from, next) => {
  clearPending()
  // ...
  next()
})

Effect demonstration:

You can see that if you click repeatedly, the request is cancelled, and if it is returned successfully, it is 200.

Principle analysis:

function CancelToken(executor) { 
 if (typeof executor !== 'function') { 
 throw new TypeError('executor must be a function.'); 
 } 
 var resolvePromise; 
 this.promise = new Promise(function promiseExecutor(resolve) { 
 resolvePromise = resolve; //把内部暴露出来 
 }); 
 var token = this; 
 //executor(cancel方法); 
 executor(function cancel(message) { 
 if (token.reason) { 
 // Cancellation has already been requested 
 return; 
 } 
 //token.reason是Cancel的实例 
 token.reason = new Cancel(message); 
 resolvePromise(token.reason);//改变promise的状态 
 }); 
 } 

In essence, the core of CancelToken is to mount the promise, and then do not actively resolve or reject it, but expose the initiative first, which is the resolvePromise in the code, and then change the promise in the cancellation function. status.

What is the use of changing this state? It needs to be understood in conjunction with the xhrAdapter source code. Here we can see where the abort was made. The red part is the process of changing the promise state above and being executed in .then.

function xhrAdapter(config) { 
 return new Promise(function dispatchXhrRequest(resolve, reject) { 
 if (config.cancelToken) { 
 //请求中,监听cancelToken中promise状态改变 
 config.cancelToken.promise.then(function onCanceled(cancel) {
 if (!request) {
 return;
 }
 request.abort();
 reject(cancel); 
 request = null; 
 }); 
 } 
 }) 
} 

Conclusion:

In fact, http request cancellation is not very unusual. To achieve similar request cancellation, there are many other methods, and even many methods are better than this one. For example, cancel the current request instead of cancel the previous one. It seems It's more logical, but this article focuses on this idea of exposing resolve, which is worth learning.

Text/Lily

Pay attention to the material technology, be the most fashionable technical person!


得物技术
846 声望1.5k 粉丝