⚠️ This article is the first contract article in the nuggets community. It is prohibited to reprint without authorization

Introduction to the

This article is the third in the mall practice series, mainly to bring you in the project development of some configuration of the interface request, convenient team development to do some business processing.

In daily development, many friends use either native Ajax ‘ ‘ ‘ ‘ ‘ ‘ ‘ ‘ ‘ ‘ ‘ ‘ ‘ ‘ ‘or popular encapsulation libraries such as AXIos and UMi-Request to make a layer of business encapsulation to do some interactive processing on requests, such as: Common operations such as Token binding, login expiration, request error message, and interface retry.

So, how do you encapsulate an interface request approach in a project that meets business requirements while reducing some of the rework?

Today, I’m going to take you through an initial project and, based on that, I’m going to go through the packaging and plug-ins of some of the common requests that interfaces handle. They can reduce the processing of some common logic during the front-end and back-end integration, improve the efficiency of business development, and help the team realize the automatic generation and management of unified interfaces.

These common packages and configurations fall into five main categories, which I’ve listed in the following figure.

In this paper, UMi-Request is selected as the main request library, which is an extended package library based on fetch request. For details, see umi-request

Initialize the project

Before we begin, in order to better set up our interface request encapsulation, we need to do some pre-work to install the corresponding tool libraries. So, I’m going to walk you through initializing our request file step by step, along with a simple configuration initialization.

First, we need to install UMi-Request and initialize the project.

// #npm
npm install --save umi-request

// #yarn
yarn add umi-request
Copy the code

Then, create a wrapper file, such as request.ts under utils.

Finally, in the file, the current request instance is initialized, the request configuration is initialized using the EXTEND method in umi-Request, and a RequestMethod instance is returned.

Here, basic parameters such as timeout, errorHandler, and request prefix are configured.

import { extend } from 'umi-request'; */ const request = extend({prefix: INTERFACE_URL, timeout: 3000, errorHandler});Copy the code

After we initialize the corresponding configuration, we generate the Request instance for the interface request function. Now, all we need to do is configure the corresponding interceptor hooks.

The interceptor

We mentioned interceptors in the initialization section, so what is an interceptor and what does it do? Many people who are new to front-end development may be confused. Here, I will talk about the mechanism of interceptor and how to configure the interceptor in our project.

First, the mechanism of the interceptor is shown below. We can use the interceptor mechanism to do some middleware processing for the request before and the response after.

You can use the API of the interceptors in the current request instance request to act as the current interceptor.

  • Request. The interceptors. Request. Use: request interceptor
  • Request. The interceptors. Response. Use: response interceptors

In the request interceptor, the request address (URL) and request configuration (options) of the current request can be modified, and then the request will use the new request configuration as the interface request.

// Request interceptor, change url or options.
request.interceptors.request.use((
  url: string, 
  options: RequestOptionsInit
) = > {
  return {
    url: `${url}&interceptors=yes`,
    options,
  }
})
Copy the code

In the response interceptor, we can do some general processing on the response result in advance. It is worth noting that we need to use Response.clone ().json() to parse the response result.

/ / clone response object analytical processing the request. Interceptors. Response. Use (async (the response: Response) => { const data = await response.clone().json(); if (data.code === 500) { throw new Error('throw error ...... ') } return response; });Copy the code

This is a brief introduction to interceptors, and later sections will expand the interceptor functionality to make more in-depth use of encapsulation.

Code Indicates the Code and service

Under the business background, we order (confirmation) system USES the Code Code to describe the corresponding order exception error identification, as shown in the figure below, each Code Code corresponding to an abnormal of confirmation of order, according to the different levels and serial number, we can in the treatment of the page, according to the back-end return Code state do strategy as well as the processing state machine.

In actual development, we use code extensively instead of the current request status return, that is, in business development, HTTP request status can be described as inheritance. So, let’s explore some of the tricks of the code system.

First, let’s look at a basic RESTFul request response structure:

{
    code: 200.message: ""
}
{
    code: 201.message: ""
}
{
    code: 202.message: ""
}
{
    code: 203.message: ""
}
Copy the code

We move the request state into response for processing. When the interface is normally accessed, the front-end only needs to perform operations according to the corresponding code. Deal with it for the state.

Therefore, we break code into the following common error states:

The first is the status of our interface request. If the status judgment is successful, the data in response will be directly returned. If it fails, error handling is performed directly.

The second is some service abnormal state, which includes system error, that is, the code error. The second is third-party services (such as wechat, Dingding) and other errors.

Thirdly, some reverse logic in our development can be agreed with custom error codes.

Below, we also enumerate some common HTTP codes as identifiers, which are sufficient in most scenarios. I’m not going to list all the public states in detail here.

Both back-end and front-end libraries are well-developed to do this

export enum HTTP_STATUS {
  SUCCESS = 200,
  CREATED = 201,
  UPDATED = 202,
  DELETED = 203,
  CLIENT_ERROR = 400,
  AUTHENTICATE = 401,
  FORBIDDEN = 403,
  NOT_FOUND = 404,
  SERVER_ERROR = 500,
  BAD_GATEWAY = 502,
  SERVICE_UNAVAILABLE = 503,
  GATEWAY_TIMEOUT = 504
}

export enum CUSTOM_HTTP_CODE {
  MESSAGE_TOAST = 601,
  SERVICE_TRY_CATCH = 602. }Copy the code

Now that we have a basic definition of code, let’s take a look at some actual usage scenarios and see how they can be used.

Identity is overdue

When you think about expired status, most of you probably think about 401 status. Most of the business logic is to clear the cache and let the user log in again.

In complex scenarios, there are many definitions of identity expiration, including Token expiration, single sign-on, identity exception, and so on. Therefore, we call the corresponding handler in the interceptor, as follows:

* [u -> 403] The user has logged in elsewhere, and a new identity is generated ** /
if (
  responseBody.code === 401 ||
  responseBody.code === 402 ||
  responseBody.code === 403
) {
  clearUserTraces();
  return undefined;
}
Copy the code

With code, we trigger clearUserTraces execution to direct the user to the login interface and complete the new authorized login.

/** * Clears the current user's identity information and jumps to the login page */
 function clearUserTraces () {
  Modal.confirm({
    title: 'Prompt message'.icon: createElement(ExclamationCircleOutlined),
    content: 'Your current device information has expired, you can cancel to remain on this page, or log in again. '.okText: "Login".cancelText: "Closed".onOk() {
      history.replace('/login')}}}Copy the code

Notification Customization

In most backend projects, notifications of different types and states are involved. In terms of ant Design’s notification bar styles, we have two notification styles, message and Notification, and different status displays.

Based on this information, we also contracted a field showMessage with the back end to manage some of the error messages. Let’s look at the current interface definition:

type CommonResult<T = any> = {
  data: T;
  code: string;
  showMessage:
    | undefined
    | false
    | {
        method: "message" | "notification";
        type: "success" | "error" | "info" | "warning";
        message: string; description? :string;
      };
};

Copy the code

The back end can operate the notification prompt of the front-end interface in the interface through the returned body model, which is mainly used for the business to make corresponding prompt for the success and failure of the operation.

So in the response interceptor, I make a judgment call about showMessage. As follows, if showMessage exist then performs showMessageNotificationHandler method processing information popup window.

if (responseBody.showMessage) {
  showMessageNotificationHandler(responseBody.showMessage)
}
Copy the code

ShowMessageNotificationHandler specific do what? Let’s look at the implementation of the method. The general implementation idea is to display different component pop-ups for the types and parameters passed to the back end in the Switch case.

/** * displays notification popup *@param ShowParams displays the notification parameter *@param API currently identifies */
export function showMessageNotificationHandler(
  showParams: API.CommonResult["showMessage"],
  api: string = ""
) :void {
  / * *@name Check whether notification description display */ is enabled
  if (showParams) {
    const { method, type, message, description } = showParams;
    switch (method) {
      / * *@name Global popup */
      case "message":
        showMessage[type](message);
        break;

      / * *@name Notification prompt popup */
      case "notification":
        notification[type]({
          message,
          description,
        });
        break;
      default:
        throw new Error(
          '[request]: Popup information parameters are specified on the interface, but the usage model is not provided. Please check the current.${api}`); }}}Copy the code

Once we’ve wrapped it, we Mock an interface to see what it looks like. We return a message popover to see what it looks like:

'GET /api/service-admin/v1/data/table': {
  code: 40001.message: 'Operation successful'.showMessage: {
    method: 'message'.type: 'error'.message: 'I'm a custom message',},data: {}}Copy the code

Error handling

In error handling, we handle some request errors. In addition to error handling of the original HTTP state, you also need to export the custom code, returning a Promise.Reject error. We can catch the current exception in a try catch and do some business code processing.

/** Exception handler */
function errorHandler(
  error: ResponseError<any> & { code? :string | number;
  }
) {
  const { response, request } = error;
  if (response && response.status) {
    const errorText = codeMessage[response.status] || response.statusText;
    const { status, url } = response;

    notification.error({
      message: 'Request error${status}: ${url}`.description: errorText,
    });
  }

  if (error.code && error.code === 5000) {
    // todo: report the error person and corresponding information
    throw new Error('[Fetch Fail]: indicates the address of the current interface${request.url}`);
  }

  return Promise.reject(error);
}

Copy the code

As shown in the following code example, we try to capture the request information of the current interface in the call layer try catch. As shown in the following code, the 40001 code is not processed in the response interceptor, so it is still not processed after flowing into the errorHandle, so it is handed over to the caller to handle:

const handleRequest = async (
  props: API.TableRequestParams
): PromiseThe < {data: API.GithubIssueItem[];
  success: boolean;
  total: number; } > = > {try {
    const res = await fetchTableList(props);
    return {
      data: res.list,
      success: true.total: res.total,
    };
  } catch (throwError: unknown) {
    console.log(throwError, "throwError");
    return {
      data: [].success: false.total: 0}; }};Copy the code

Configure the mapping

When we had a wrapped interface request function, I also wrapped a configuration mapping UMI plug-in to handle the interface function and interface specification conversion for the convenience of team members to call.

In the figure below, we declare a configuration map and then abstract it into a function. Easy to call at development time.

Here to give you a specific way to use.

First, I declare a configuration file in the Services directory. The standard form of this configuration file is an object, which you can also use as a JSON file. The convention Key value is the name of the mapping function. Value indicates the request mode and address.

The Object of writing

module.exports = {
  fetchTableList: 'GET /service-admin/v1/data/table'
}
Copy the code

JSON notation

{
  "fetchLogin": "POST /service-admin/v1/user/login"."fetchUserInfo": "POST /service-admin/v1/user/info"
}
Copy the code

When we have the configuration file, the corresponding request function is generated when we start the project, as shown below:

The plug-in converts the corresponding configuration file into an interface request function. So how do I implement this functionality with the help of plugins? Here’s a look at the core of the plug-in

We convert the interface configurations in different directories into corresponding configurations and output them to the template template to complete the mapping.

Map(1) {
  '/Users/wangly19/Desktop/Project/plugins/example/./constant/a.ts' => [
    {
      functionName: 'a'.url: '/app-services/${id}'.method: 'post'.linkParams: [Array].exportTemp: 'const { id, ... data } = payload'.paramType: 'data'}}]Copy the code

This is the configuration we generated, so let’s take a look at the template.

We can render the values in the Map into the template below, and finally output a complete interface function file.

import request from '{{{ requestPath }}}'
import { RequestOptionsInit } from 'umi-request'


{{#requestASTModules }}

export function {{ functionName }} <T = any, R = any>( payload? : T = {}, options? : RequestOptionsInit = {}, ): Promise<R> {

  {{#exportTemp}}
    {{{ exportTemp }}}
  {{/exportTemp}}
  {{^exportTemp}}
    /* [info]: @no link params */
  {{/exportTemp}}
  
  return request( `{{{ url }}}`, {
    {{ paramType }}: {{#exportTemp}}data{{/exportTemp}}{{^exportTemp}}payload{{/exportTemp}}, method: '{{ method }}',... options }) }{{ /requestASTModules }}
Copy the code

Let’s take a look at the effect, as shown below:

resources

  • # umi-request Indicates the route of network request
  • # Understand RESTful architecture
  • Xios encapsulation and API interface management

conclusion

This article has done some contractual encapsulation of requests in the team, some basic encapsulation of business scenarios that might be encountered in daily development, and a series of interactions that distinguish interface errors from success. 🤔 explains how flexibility is managed and controlled in team business. At the same time, the interface request management configuration mapping is encapsulated as a plug-in under the decoupling of team development. Team members do not need to care about the diversity of request methods and can focus on business development and interface invocation.

Finally, we tried some other scenarios as well. Later, we can continue to extend the optimization, I listed these points below:

  • Interface request retry mechanism, can be onrequestBusiness logic to retry after failure
  • When the page leaves, the interface request stops automatically and can passcancelTokenwithcontroller.abortTo cancel the interface request
  • The interface management platform is used to generate interface mapping configurations with one click, reducing manual work
  • Interfacing with Swagger generates typescript partial interface request types

These are some of the most common encapsulation techniques used to improve team effectiveness. For teams, more general-logic behavior can be applied to requests with reasonable agreement on both sides, which can often work wonders in mid-back projects.

For a mall project, its challenge is not in the implementation of the logic of the function, but in part of the visual experience and experience optimization. If you think the article is helpful to you, you can click 👍 and give me a fuel. If you want to know more about the front-end e-commerce project, you can follow this column.

The recent oliver

  • # million PV mall practice series – front-end picture resource optimization combat
  • # [collection will be] browser WebStorage cache usage guide
  • # me & the Nuggets, one year after graduation, I was signed by the Nuggets | mid-2021 summary
  • # Summarize the practical experience of TypeScript application in project development

endnotes

This article was first published in: Nuggets of technology community type: contract article author: Wangly19 collected in column: # million PV mall practice series public number: ItCodes app life