The author is Li Tou Ant Financial · Data experience technology team

background

In the process of developing the middle platform business application, we found the following problems on the request link:

  1. Request libraries are varied and not uniform. Each new application needs to implement a set of request layer logic repeatedly, and the request library API needs to be relearned when switching applications.
  2. The design of application interfaces is inconsistent and chaotic. The front and back end students need to redesign the interface format each time, and the front end students need to understand the interface format again when switching applications before they can do business development.
  3. Interface documentation is maintained in a variety of ways. Some are in the Cloud, some are in RAP, some are by reading the source code, maintenance, mock data and communication is a waste of manpower.

To solve above problems, we put forward the request layer of governance, the hope is that a unified request library, standard interface design specification, unified interface document this three steps, the request in the link of the former three stages after effect and the specification, thereby reducing the developer in the interface design, documentation, maintenance, request layer on the logic development of communication and human cost. As the technical support of the bottom layer, the unified request library needs to set up a base in advance to provide stable and perfect function support for the upper layer. Based on this, UMI-Request emerged at the right moment.

umi-request

Umi-request is an open source HTTP request library encapsulated by FETCH. It aims to provide developers with a unified API call method, simplify usage, and provide common functions of the request layer:

  • Automatic serialization of URL parameters
  • Simplified POST data submission
  • Response Simplified return processing
  • Request timeout processing
  • Request cache support
  • GBK coding processing
  • Unified error handling
  • Request unsupport
  • Node environment HTTP request
  • Interceptor mechanism
  • Onion Middleware Mechanism

The similarities and differences with Fetch and Axios?

features umi-request fetch axios
implementation fetch Browser native support XMLHttpRequest
The query to simplify
Post to simplify
timeout
The cache
Error checking
Error handling
The interceptor
The prefix
The suffix
Deal with GBK
The middleware
Cancel the request

The underlying layer of UMi-Request abandons XMLHttpRequest, which is poorly designed and does not meet the separation of concerns, and chooses fetch, which is more semantic and based on standard Promise implementation (see more details). At the same time, isomorphic-FETCH (currently built-in) is used for more convenient isomorphism. It extracts common request capabilities based on service application scenarios and supports quick configurations such as POST simplification, prefix and suffix, and error checking.

Convenient to fit

The installation

npm install --save umi-request
Copy the code

Performing a GET request

import request from "umi-request";
request
  .get("/api/v1/xxx? id=1")
  .then(function(response) {
    console.log(response);
  })
  .catch(function(error) {
    console.log(error);
  });
// You can also put the URL parameters in options.params
request
  .get("/api/v1/xxx", {
    params: {
      id: 1
    }
  })
  .then(function(response) {
    console.log(response);
  })
  .catch(function(error) {
    console.log(error);
  });
Copy the code

Performing a POST request

import request from "umi-request";
request
  .post("/api/v1/user", {
    data: {
      name: "Mike"
    }
  })
  .then(function(response) {
    console.log(response);
  })
  .catch(function(error) {
    console.log(error);
  });
Copy the code

Instantiate the common configuration

Requests generally have some common configuration. We don’t want to add common prefixes, suffixes, headers, exception handling, etc. to each request. We can extend to create a new instance of Umi-Request to reduce the amount of repeated code:

import { extend } from "umi-request";

const request = extend({
  prefix: "/api/v1".suffix: ".json".timeout: 1000.headers: {
    "Content-Type": "multipart/form-data"
  },
  params: {
    token: "xxx" // All requests take the token argument by default
  },
  errorHandler: function(error) {
    /* Exception handling */}}); request .get("/user")
  .then(function(response) {
    console.log(response);
  })
  .catch(function(error) {
    console.log(error);
  });
Copy the code

Built-in common request capabilities

Fetch itself does not provide request timeout, cache, cancellation and other capabilities, which are often needed in business development. Therefore, UMI-Request encapsulates and builds common request capabilities to reduce repeated development:

{
  // 'params'Params: {id: 1}, // Is a URL parameter to be sent with the request, which is automatically encoded into the URL'paramsSerializer'This function allows developers to serialize params (note: The params passed in is an Object that incorporates the params parameter from extends. If the URLSearchParams Object is passed in, it is converted to the paramsSerializer Object:function (params) {
    return Qs.stringify(params, { arrayFormat: 'brackets'/ /})}'data'The data that is sent as the body of the request // applies to these request methods'PUT'.'POST', and'PATCH'// Must be one of the following types: // -string, plain Object, ArrayBuffer, ArrayBufferView, URLSearchParams // - browser specific: FormData, File, Blob // -node exclusive: Stream data: {name:'Mike' },

  // 'headers'Headers: {'Content-Type': 'multipart/form-data' },

  // 'timeout'Specifies the number of milliseconds for the request to timeout (0 means no timeout) // If the request exceeds the timeout limit'timeout'Timeout: 1000, // The request will be interrupted and a request exception will be thrown'prefix'// (e.g. request('/user/save', { prefix: '/api/v1' }) => request('/api/v1/user/save') )
  prefix: ' ', / /'suffix'// (e.g. request('/api/v1/user/save', { suffix: '.json'}) => request('/api/v1/user/save.json') )
  suffix: ' ', / /'credentials'To enable the browser to send requests containing credentials (even from cross-domain sources), you need to set the credentials:'include'// Add the credentials if you only want to send credentials when the request URL is at the same origin as the calling script:'same-origin'To ensure that the browser does not include credentials in the request instead, use the credentials:'omit'
  credentials: 'same-origin', // default

  // 'useCache'Whether to use cache, when istrue, the GET request will be cached within TTL milliseconds. The unique key of the cache policy is url + params useCache:false, // default

  // 'ttl'Cache duration (ms). 0 indicates that the cache does not expire. TTL: 60000, //'maxCache'MaxCache: 0, //'charset'This parameter can be used when the encoding type of the data returned by the server is GBK. Umi-request parses the data according to GBK to avoid garbled characters. The default value is UTf8 // when parseResponse isfalseCharset:'gbk', / /'responseType': How to parse the returned data when parseResponse value isfalse// The default value is'json'Parse (d => json.parse (d)) // other (text, blob, arrayBuffer, formData), ResponseType [responseType]()'json', // default

  // 'errorHandler'Unified exception handling for developers to uniformly handle exceptions occurred in requests. For details, please refer to the following error handling document errorHandler:function(error) {/* Exception handling */},}Copy the code

Middleware mechanisms are easy to extend

Complex scenarios require customized processing before and after requests. In addition to providing basic built-in capabilities, request library also needs to improve its scalability. Based on this, UMi-Request introduces ** middleware mechanism and ** selects koA-like onion ring model:


As can be seen from the above figure, each layer of onion rings is a middleware, and requests will be executed twice by a middleware. Developers can easily implement enhanced processing before and after requests according to business requirements:

import request from "umi-request";

request.use(function(ctx, next) {
  console.log("a1");
  return next().then(function() {
    console.log("a2");
  });
});
request.use(function(ctx, next) {
  console.log("b1");
  return next().then(function() {
    console.log("b2");
  });
});
// The execution sequence is as follows:
// a1 -> b1 -> b2 -> a2

// Use async/await to make structure and order clearer:
request.use(async (ctx, next) => {
  console.log("a1");
  await next();
  console.log("a2");
});
request.use(async (ctx, next) => {
  console.log("b1");
  await next();
  console.log("b2");
});

const data = await request("/api/v1/a");

// The execution sequence is as follows:
// a1 -> b1 -> b2 -> a2
Copy the code

Realize the principle of

So how does the middleware mechanism of onion rings work? It is mainly composed of middleware array and middleware combination. The former is responsible for storing the mounted middleware, and the latter is responsible for combining the middleware according to the structure of the onion and returning the real executable function:

Storage middleware

class Onion {
  constructor() {
    this.middlewares = [];
  }
  // Storage middleware
  use(newMiddleware) {
    this.middlewares.push(newMiddleware);
  }
  // Execute middleware
  execute(params = null) {
    const fn = compose(this.middlewares);
    returnfn(params); }}Copy the code

Combined middleware

The compose in the above code is the function implementation of the composite middleware. The condensed logic is as follows (see “compose”) :

export default function compose(middlewares) {
  return function wrapMiddlewares(params) {
    let index = - 1;
    function dispatch(i) {
      index = i;
      const fn = middlewares[i];
      if(! fn)return Promise.resolve();
      return Promise.resolve(fn(params, () => dispatch(i + 1)));
    }
    return dispatch(0);
  };
}
Copy the code

The compose function executes the first middleware fn(Params, () => dispatch(I +1)) and provide () => Dispatch (I +1) as an input so that each middleware can come back after the next middleware has finished and continue processing its own transaction until promise.resolve () is executed after all middleware has finished. Form the onion ring middleware mechanism.

Enrich request capability

For multi-terminal and multi-device applications, UMI-Request not only supports Browser HTTP requests, but also meets node environment and customized kernel requests.

Supports the Node environment to send HTTP requests

Implementation of request support for Node environment based on isomorphic-FETCH:

const umi = require("umi-request");
const extendRequest = umi.extend({ timeout: 10000 });

extendRequest("/api/user")
  .then(res= > {
    console.log(res);
  })
  .catch(err= > {
    console.log(err);
  });
Copy the code

Support for custom kernel request capability

Mobile applications generally have their own request protocols, such as RPC requests. The front-end will call the client request API through SDK. Umi-request supports developers to encapsulate request capabilities by themselves.

// service/some.js
import request from "umi-request";
// Customize request kernel middleware
function SDKRequest(ctx, next) {
  const { req } = ctx;
  const { url, options } = req;
  const { __umiRequestCoreType__ = "normal" } = options;

  if(__umiRequestCoreType__.toLowerCase() ! = ="SDKRequest") {
    return next();
  }

  return Promise.resolve()
    .then((a)= > {
      return SDK.request(url, options); // Assume that the SDK has been introduced and that requests can be made through the SDK
    })
    .then(result= > {
      ctx.res = result; // Inject the result into CTX's res
      return next();
    });
}

request.use(SDKRequest, { core: true }); // Introduce kernel middleware

export async function queryUser() {
  return request("/api/sdk/request", {
    __umiRequestCoreType__: "SDKRequest".// Declare that SDKRequest is used to initiate the request
    data: []
  });
}
Copy the code

conclusion

With the improvement of UMI-request capability, it has been able to support requests of various scenarios and end applications. Front-end development only needs to master a set of API calls to achieve multi-terminal development, and no longer needs to pay attention to the implementation of the underlying protocol, putting more energy on front-end development. Based on this, UMI-Request can do more things at the bottom layer, such as mock data, automatic identification of request types, interface exception monitoring and reporting, interface specification verification and so on, and finally achieve the goal of request governance. There are many capabilities of Umi-Request that have not been mentioned in this article. If you are interested, please check the detailed documentation. If you have good suggestions and requirements, please also make an issue.

Giuthub blog post link