Original address:Monine. Making. IO / # / article / 2…

I have been busy with my work recently. This article should have been produced two months ago, but I spent most of my energy on my work every day. As SOON as I wrote the article, I was confused. This article is a basic requirement encountered in the background project of our company. I designed an implementation scheme by myself, and I feel good about it.

demand

The back-end interface responds and determines whether the interface is abnormal according to the status code (non-HTTP status code) agreed with the back-end. Our company’s convention is status! If == 0, the interface is abnormal. Once the interface is abnormal, the business end (caller) should handle the exception first, and then the business end should decide whether to perform unified handling of interface exceptions. (Currently, the unified handling content of our company is to pop up an element-UI message prompt message 😂).

There is a difficulty in this process. When the interface is in abnormal state after responding, it is first handed over to the business end for processing, and then the business end decides whether to implement unified processing.

For the API layer, our company uses the third-party library AXIOS. After the interface responds, the interceptor will be used first, and then the business code will be used. The normal unified handling process for interface exceptions is to determine whether the response status code agreed with the back end is an exception status code in the response interceptor. If yes, the unified processing logic is executed first and then transferred to the business side for processing. Therefore, the current requirement is to reverse the interface exception processing process. When the interface response status is abnormal, the business end performs the exception processing first, and then the business end decides whether to perform the unified processing of interface exception status.

As mentioned above, if the interface is abnormal, you need to determine whether to perform unified processing in two cases:

  1. If the business side does not handle exceptions, it must perform unified processing.
  2. The business side has handled the exception and proactively declares whether to continue the unified processing. (How should an active statement be designed?)

The question is, where should I write the code for unified handling of interface exceptions? How to ensure that it is handled by the business end in case of abnormal status, and then decide whether to implement unified processing according to the statement of the business end?

Mixin of historical solutions

Our company has had a processing scheme before, but it is only for the processing under the VUE framework. All methods within the methods are overwritten by mixin, and the return result obtained after the execution of the source function is judged within the patch function. If the return result is of the Promise type, relevant exception processing operations will continue. This does work, but it always feels clunky:

  1. The processing of the API layer is deeply tied to the framework, which in itself makes no sense.
  2. All functions in methods are overwritten, with a lot of useless overhead; If the function name convention is implemented, override filtering is added, which increases the cost of the convention.
  3. Can only be applied to vUE framework, can not be directly used under other projects, very limited.

When I learned about the above solution, my first reaction was that none of the API layer operations should be associated with the framework itself, just as Vue removed vue-Resource from the family bucket.

My solution

After some thinking, a general idea is determined: using the stability of the Promise state, the interface name as a unique identifier, indicating whether the current interface still needs to perform unified processing.

My design is that the interface is called using a URL as a unique identifier, stored in an array as a state const unhandleAPI = [].

After the interface returns, it enters the response interceptor, where the interface response status is judged. If it is abnormal, setTimeout is used to set the unified handling function of the interface exception as Macro Task, which is postponed to the next round of Event Loop as an asynchronous task. And returns promise.reject. Then it will enter the catch callback function of the calling code of the business side interface. If there is no return value after executing the business exception processing, it means that unified processing is not needed. Conversely, returning a non-undefined value indicates that unified processing also needs to be performed.

Before performing unified handling of interface exceptions, check whether THE URL identifier exists in unhandleAPI. If so, perform unified handling.

The above is a general design idea, specific to the realization of the need to solve some practical problems:

  1. How do YOU determine the uniqueness of an interface? Because the same interface can be called multiple times in milliseconds.
  2. How is the exception state of an interface saved?
  3. How to remove abnormal interface status when appropriate? For example, the business side does not want to perform unified processing after handling the exception.

The specific implementation

  • Interface exception processing status storage

    Use an array object const unhandleAPI = [] to hold the unique identifier of all interfaces that have been called but not yet responded to. In addition, expose some interfaces to operate the unhandleAPI.

    const unhandleAPI = [];
    
    if(process.env.NODE_ENV ! = ='production') {
      window.unhandleAPI = unhandleAPI;
    }
    
    export function matchUnhandleAPI(id) {
      return unhandleAPI.find(apiUid= > apiUid === id);
    }
    
    export function addUnhandleAPI(id) {
      unhandleAPI.push(id);
    }
    
    export function removeUnhandleAPI(id) {
      const index = unhandleAPI.findIndex(apiUid= > apiUid === id);
    
      if (process.env.NODE_ENV === 'production') {
        unhandleAPI.splice(index, 1);
      } else {
        // Make it easier for non-production environments to view interface processing
        unhandleAPI[index] += '#removed'; }}Copy the code
  • Sending interface request

    By looking at the axios source, know axios real call interface method is axios. Axios. Prototype. Request, so need to be written. Add the current call the unique identifier of the interface to the unhandleAPI array object, but also to be added to axios. Axios. Prototype. The request method returned by the Promise of instance objects (interface) response after treatment will have access to.

    let uid = 0;
    const axiosRequest = axios.Axios.prototype.request;
    axios.Axios.prototype.request = function(config) {
      uid += 1;
    
      const apiUid = `${config.url}? uid=${uid}`; // The unique identifier of the interface call
      config.apiUid = apiUid; // The response interceptor needs to use the apiUid, so add it to the config attribute
      addUnhandleAPI(apiUid); // Add an array object to the interface exception handling state store
    
      const p = axiosRequest.call(this, config); // Trigger the AXIOS interface call
      p.apiUid = apiUid; // Add a unique identifying attribute to the Promise instance returned by the current interface call
      return p;
    };
    Copy the code
  • The interface response goes into the response interceptor

    Determines the interface state in the response interceptor and, if normal, removes the unique identity of the current responding interface from the array object in the interface state store. If an exception occurs, setTimeout delays the execution of the interface state exception handler and returns promise.reject () to the business end.

    service.interceptors.response.use(
      ({ data, config }) = > {
        const { status, msg, data: result } = data;
    
        // Check whether the interface status is abnormal
        if(status ! = =0) {
          const pr = Promise.reject(data);
          pr.apiUid = config.apiUid; // Add a unique identifying attribute for the current interface to the Promise instance
          setTimeout(handleAPIStatusError, 0, pr, msg); // Exceptions are sent to the business side for processing, delaying the execution of the unified handling function
          return pr;
        }
    
        // The interface is normal
        removeUnhandleAPI(config.apiUid); // Removes the unique identifier of the current responding interface from the array object in the interface exception handling status store
        return result;
      },
      error => {
        Message.error(error.message);
        return Promise.reject(error); });Copy the code
  • Business side processing

    Now assume that the interface state is an exception. After passing through the response interceptor, the code executes to the business side. Let’s look at the code calling the business side interface:

    callAPIMethod().catch(error= > {
      // The business side handles the exception
    });
    Copy the code

    This is the general syntax for a Promise catch. If the Promise status returned by callAPIMethod is Rejected, the callback of the catch function is executed.

    Remember the procedural difficulties mentioned above?

    The business side decides whether to execute the unified interface exception handler, so it needs to be designed here. How to declare the callback function of the catch function? As mentioned above, the declared design uses the return value of the catch callback.

    The design scheme is OK. How to write the code when it comes to the concrete implementation? No doubt, the catch function needs to be overridden:

    Promise.prototype.catch = function(onRejected) {
      function $onRejected(. args) {
        constcatchResult = onRejected(... args);if (catchResult === undefined && this.apiUid) {
          removeUnhandleAPI(this.apiUid); }}return this.then(null, $onRejected.bind(this));
    };
    Copy the code

    In fact, the catch method itself is just syntax sugar. The callback function of the catch function is wrapped. In the wrapped function, the callback function of the business side catch is executed first and the execution result of the function is obtained. Then, if there is an apiUid attribute on the current Promise object, the current Promise is an API layer promise. If the catch callback returns undefined, the interface exception state handler is no longer needed and the unique identifier of the current interface needs to be removed from the previously defined unhandleAPI array.

  • The then method returns a new promise

    The above business-side processing may seem normal, but in most cases, the business-side code does not chain-call the catch method directly after the interface call. Instead, it calls the THEN method and then the catch method, as follows:

    callAPIMethod()
      .then(response= > {
        // ...
      })
      .catch(error= > {
        // ...
      });
    Copy the code

    The result of callAPIMethod() returns a Promise object with the apiUid attribute indicating that the current Promise is an API layer interface. Then the then method and catch method are called in chain. Because the then method is inserted in the middle, the attribute of this object in the overwrite function of catch does not have the apiUid attribute, so it cannot determine that the current promise is the return object of the API layer interface. The reason is that the THEN method returns a new Promise instance after execution, so you need to override the THEN method as well.

    const promiseThen = Promise.prototype.then;
    Promise.prototype.then = function(onFulfilled, onRejected) {
      // Get the new Promise instance object returned by the then method
      const p = promiseThen.call(this, onFulfilled, onRejected);
      // In the case of an apiUid on a Promise object, this represents an interface layer promise
      // Add the apiUid to the Promise instance object returned by the THEN method
      if (this.apiUid) p.apiUid = this.apiUid;
      return p;
    };
    Copy the code

    In the override function of the THEN method, the original THEN method is executed, the result is returned, and the current caller promise object is determined to have the apiUid attribute. If so, it represents an API layer Promise, which requires the apiUid attribute to be added to the Promise instance returned by the current THEN method.

  • Execute the unified interface exception status handler

    In the case of abnormal interface status, if the business end proactively declares that it needs to perform unified handling of abnormal interface status (the business end catch callback returns a non-undefined value), the handleAPIStatusError function that is delayed by setTimeout in the interceptor is executed

    As long as the interface response status is abnormal, the unified handling function of the interface abnormal status will be executed, and the internal judgment will be made

    function handleAPIStatusError(pr, msg) {
      const index = unhandleAPI.findIndex(apiUid= > apiUid === pr.apiUid);
      if (index >= 0) {
        pr.catch((a)= > {
          Message.error({ message: msg, duration: 5e3}); }); }}Copy the code

    If pr.apiUid can be found in the unhandleAPI array object, then unified handling of the interface exception state is required.

Possible problems

If the project is a Webpack template project built by VUe-CLI, this scheme will not work in Firefox without modifying the configuration of the.babelrc file. If the interface status is abnormal, the unified processing is always performed. The service end is not assigned to handle the exception before deciding whether to perform the unified processing.

The reasons and solutions for invalidation in Firefox will be covered in the next article.

Self assessment

Personally, I think such a design is very elegant, the cognitive cost is very small, and there is no pollution to the routine development of partners; There is no dependency on the framework and can be ported to any framework project.

In addition

Limited ability, which partner has a more elegant and appropriate program is also expected to be generous to advise.