Before the development of wechat applets, I have never been exposed to the development process involving third-party server interaction. In the development process itself is not much of a surprise, just in the maintenance of server login status this point is annoying. Because it involves the tripartite relationship between the login state of its own server and the login state of wechat official server.

Below is the wechat login mechanism:

In this scenario, the point of personal concern is how to log in without awareness (and without unnecessary requests). The login status of wechat is easy to solve, you can use wx.checkSession to determine, but when interacting with the background server, if the background interaction returns HTTP status code 401 (unauthorized) or other not logged in indication. Additional processing is required.

At that time, I remembered that in order to solve this problem elegantly, I had thought of many solutions and discussed this problem with some partners. Although there was no perceptive login, it either required a multi-request server, or the implementation logic was too complicated and the code was maintained. I was not satisfied, but I did not think of a good solution at that time.

WeRequest has its own request component for state management

Later, when I saw this component introduced by the eldest brother, I suddenly saw that it was just the solution I needed. The solution is shown as follows:

You just need to configure a few initialization items, and you’re ready to go.

// Import import weRequest from'we-request'; Werequest.init ({// [optional] existslocalThe session name of the Storage, and the CGI request data will automatically carry this name of the session value; This parameter is optional. The default value is session sessionName."session"// [optional] Request URL fixed prefix; UrlPerfix:"https://www.example.com/", // [required] Conditions that trigger re-login, res is the data returned by CGI loginTrigger:function(res) {// Here's an example: When the field errcode in the returned data equals -1, a re-login is automatically triggeredreturnres.errcode == -1; }, // [required] exchange code for session CGI configuration codeToSession: {// [required] CGI URL URL:'user/login'// [optional] Call the CGI method; The default value is GET method.'GET'// [optional] The name of the code stored in the CGI parameter. In this example, the name is code. This parameter is optional. The default value is code codeName:'code', // [Optional] Other parameters required by the login interface; This parameter is optional. The default value is {} data: {}, // [required] session value returned from CGI SUCCESS:function(res) {// Here's an example: CGI returns the session field in the data as the session valuereturnres.session; }}, // [Optional] Number of login retries. If the number of consecutive login attempts returned by the interface exceeds this number, the login will not be retried. This parameter is optional. The default value is 3 reLoginLimit: 2, // [Required] successTrigger:function(res) {// In this example, if the errcode field in the returned data is 0, the request succeeds. In other cases, the service logic failsreturnres.errcode == 0; }, // [optional] Return data on success; SuccessData is optional:function(res) {// In this example, the returned data field data is the data received by the servicereturnres.data; }, // [optional] When CGI returns an error, the pop-up prompts the title text errorTitle:function(res) {// Here is an example: If the field errCode in the returned data is equal to 0x10040730, the title of the error pop-up box is "Warm warning", otherwise it is "Operation failed".return res.errcode == 0x10040730 ? 'Warm reminder' : 'Operation failed'}, // [optional] When CGI returns an error, the pop-up prompts the content text errorContent:function(res) {// In this example, the field MSG in the returned data is the prompt content text of the error pop-up boxreturn res.msg ? res.msg : 'Service may be abnormal. Please try again later.'}, // [optional] A unified callback function when a CGI error occurs, where you can do a unified error reporting, etc. ErrorCallback:function(obj, res) {
        // doSome report}, // [Optional] Whether to call checkSession to verify that the login state of the applet expires. This parameter is optionalfalse
    doNotCheckSession: true// [Optional] Function reporting time. Name is the reporting name, startTime is the timestamp when the interface calls, endTime is the timestamp reportCGI when the interface returns:function(name, startTime, endTime, request) {
        //wx.reportAnalytics(name, {
        //    time: endTime - startTime
        //});
        //request({
        //    url: 'reportCGI',
        //    data: {
        //        name: name,
        //        cost: endTime - startTime
        //    },
        //    fail: function() {
        //
        //    }
        //})
        console.log(name + ":"+ (endTime - startTime)); }, // [optional] Provide the mock interface. If you do not need to use it, set it tofalse. Url is the URL at which werequest.request () was called. The mock data is formatted in accordance with the data format provided by the formal interface. mockJson: { url1: require(".. /.. /mock1.json"),
        url2: require(".. /.. /mock2.json"),
        url3: require(".. /.. /mock3.json"} // [optional] All requests will automatically take the globalData parameter:function() {
        return{version: getApp().version}}, // [Optional] Session Local cache duration (unit: ms). This parameter is optional. SessionExpireTime: 24 * 60 * 60 * 1000, // [Optional] Session Local cache duration The Storage name is optional. The default value is sessionExpireKey."sessionExpireKey"
})

export default weRequest;
Copy the code

The weRequest can be used directly when using

weRequest.request({
    url: 'order/detail',
    data: {
      id: '107B7615E04AE64CFC10'
    },
    method: 'GET'}). Then ((data)=>{// omit... })Copy the code

The code is briefly

A brief introduction to the implementation mechanism of the weRequest library. Here the code is simplified and only the three main functions will be described.

  • Requesthandler. request Manages requests, that is, this function is executed on each request

  • Sessionmanager. main Manages session status. Session setup and deletion, and the identifier is set when the session is first acknowledged, that is, it is executed only when the login state is missing for the first time or an error occurs.

  • Responsehandler. response Manages the returned data, parses the returned data, if there is no login state, deletes the session, rerequests, and uses the second sessionManager.main to do so.

/ / requestHandler. Request methodfunction request(obj: IRequestOption): any {
  returnNew Promise((resolve, reject) => {obj = preDo(obj); // Read the session, if the session is ok. Sessionmanager.main ().then(() => {// Start the business requestreturn doRequest(obj)}).then((res) => {responseHandler.responselet response = responseHandler(res as wx.RequestSuccessCallbackResult, obj, 'request');

      if(response ! = null) {// Returns the request resultreturnresolve(response); Catch ((e) => {catchHandler(e, obj, reject)})})} // sessionManager.main methodfunction main() {
    returnNew Promise((resolve, reject) => {// Check the login state and return, if the login state expires, return successreturnCheckLogin ().then(() => {// If the login is ok, set config.donotCheckSession totrue. Avoid checking again next timereturn config.doNotCheckSession ? Promise.resolve() : checkSession()
        }, ({title, content}) => {
            errorHandler.doError(title, content);
            returnreject({title, content}); }). Then (() => {// Check the checkSessionreturn resolve();
        }, ({title, content})=> {
            errorHandler.doError(title, content);
            returnreject({title, content}); })})} // responseHandler.responsefunction response ( res: wx.RequestSuccessCallbackResult,
    obj: IRequestOption,
    method: "request") {

      if(res.statusCode === 200) { // ... Omitted code // The login state is invalid, and the retry times do not exceed the configured valueif(config.loginTrigger! (res.data) && obj.reLoginCount ! == undefined && obj.reLoginCount < config.reLoginLimit!) {/ / delete the session our sessionManager. DelSession ();if (method === "request") {// request againreturnrequestHandler.request(obj as IRequestOption); }}}}Copy the code

We can analyze the code by using the ICONS on the official website

If the user never logs in, or checkSession expires:

  • Request Specifies the API required by a direct request
  • Sessionmanager. main checks login status, that is, checkSession expiration
  • IsSessionExpireOrEmpty If session expires or is empty (currently empty)
  • Wx. login -> code2Session login to both servers
  • After success, set the identifier doNotCheckSession to continue the request

User login status has not expired, open the small program again:

  • Request Specifies the API required by a direct request
  • Sessionmanager. main check login state, see doNotCheckSession
  • Continue with the first request request

After a user logs in to the backend, the backend login state expires:

  • Request Specifies the API required by a direct request
  • Sessionmanager. main checks login status, that is, checkSession expiration
  • IsSessionExpireOrEmpty If session expires or is empty (currently not empty)
  • After success, set the identifier doNotCheckSession to continue the first request
  • The background returns an error code, which is parsed by responseHandler.response. Error found, delete session, repeat request.

New Promise internally encapsulates asynchronous operations

When writing about asynchronous code operations, it was common to use AXIos to return API request response data directly for normal and error handling. The process of multiple asynchronous operations that returned right and wrong was rarely combed. If there are multiple asynchronous operations within a single request: the code becomes unmaintainable. In fact, we can think of promises as state machines. Only in certain cases will the correct return be made.

// Asynchronous operation encapsulationfunction asyncCompnent(opt: any) {
  returnNew Promise((resolve, reject) => {// new Promise((resolve, reject) => { Reject (error)})} asyncComponent(data).then(result => {// normal process}).catch(error => {// error process})Copy the code

By writing the code above, you can operate on many business items, such as operations that have pre-permission requests, or errors that require rerequests or burying points.

One might think that there are interceptor interceptors in HTTP request frameworks, and that there is no new Promise to judge and act on. But often interceptors are global to the code, and it is not a good idea to write a lot of if judgments and business processing in interceptors for certain modules alone. Because of the business variability in the scenario, the global code is heavily modified, which is not conducive to the maintenance of the project. However, if the solution is not used properly, the controllability of the business code can be reduced.

Of course, the above code can also be processed with async and await. It is suggested to study more async error handling. I recommend two blog posts on async error handling. (I have always disliked async functions that need to be combined with callbacks or promise.reject to handle errors, so in general I prefer async to handle asynchronous operations that are not API requests. There is less need to handle errors). How to gracefully use Async and Await error handling in Javascript? Never use try-catch async/await syntax to say error handling

While reading the MobX Quick Start Guide, I came across a formula

   VirtualDOM = fn (props, state) 
Copy the code

As long as you enter the same properties and states, you will always get the same VirtualDOM data.

But WHAT I want to say is that for a business, without considering interface aesthetics and the necessary intermediate states, I think the following formula is true:

Front-end business encapsulation = management (interaction state, data state, configuration items)Copy the code

The combination of interaction state and data state is for the end user, and what the user sees depends on the first two. The last configuration is for developers, how many scenarios can your code support. How much code can be reduced by configuration.

Is it possible to enter the same interaction and data into the same business? Of course not, because there is always unknown data state from the front end. We can only deal with unclear data state through defensive programming and error handling (by adding various interactive states to solve the unknown data state).

For weRequest library, the login state of wechat is saved in storage, and the whole library maintains the login state of wechat (the interaction state with the background is not saved, as long as there is no permission state, the wechat login state will be deleted and login again). So without the code, the entire interaction state is the session stored in memory and Storage, doNotCheckSession. The data state is the API configuration we need to request and the back-end state we don’t know.

The session,doNotCheckSession // Variable interaction state werequest.init ({// fixed interaction state (configuration item) //... }) { url:'.. / '} // Data statusCopy the code

Here’s a reference to the front-end Axios rerequest scenario, which is much clearer than weRequest:

The AXIos request timed out, setting up the perfect solution to the rerequest

Also for our business code (internal component implementation), there is often some data that is also a configuration item. If you have a clear understanding of all three and manage them well, you can write business code that is not bad.

The advantage of statelessness

It’s easy to see in the Request code that the code maintains the state of the back end not because it holds a session on the back end, but rather as a trial-and-error mechanism. As long as the data hasn’t changed between the last request and the next, there’s no problem with repeating requests in error handling. At the same time, although there is a session, the doNotCheckSession data is removed from the non-business, and is only used for interaction. So what I’m trying to say here is, it’s easier to think about how to reduce the intermediate state, which is destruction, and build a new model.

At the beginning of dealing with business code, I always dealt with state more and used the explicit and implicit of components to control Dialog. Sometimes it was troublesome to fill in form forms because some mechanisms of components were not perfect at that time. After processing a form, reset could not clear the verification error of the last data. You had to write a little bit more code to get things done, and then it started to change, and for most scenarios, it was better to just destroy, create, and not manage intermediate states.

In fact, in the front-end business, in fact, there are many such examples, dealing with the relationship between the child component and the parent component, and even on the architectural side, changing a single page application to a multi-page application based on the business, is also a destruction, new mode. Use the browser’s own mechanisms to remove most of the intermediate states.

When we are struggling to maintain an intermediate state, using various tools to improve performance, we might as well consider the following, whether removal is a simpler solution.

The reason for this feeling is that I once made a requirement with 5 or 6 complex function points when I was just graduated, and now I need to add another function point. But there was no way to add more code to solve the problem. It had to be broken up and reorganized. Encounter this kind of thing, some friends may wonder if the code was not written well at that time. It wasn’t that the code was bad, it was that the previous code was pretty good, fit pretty well, didn’t know how to do it, and I had no control over it as a beginner (it required a thorough consideration of all the features, and complexity was high), so I completely lost control of the requirement. So, the ability to fit complex code, considering all the possibilities, is the ability. Being able to parse complex code, make certain sacrifice decisions, and simplify complexity is also an ability. The ability of the former is the strength of personal ability, which cannot be copied and replaced. The latter is to make the team implementation easier, faster implementation of various functions. It is important for a mature programmer to improve on both.

If the advantage of statelessness is simple on the front end, the advantage of statelessness on the back end of the Web is even greater. It can be expanded horizontally through external extensions, which essentially removes the interaction state (user data) to other media so that requests can be sent to different servers, rather than to a single server. At the same time, each request is unique, and there is no need to consider the intermediate state of migration, which is conducive to the speed and correctness of development.

WeRequest source code structure analysis

WeRequest is a very small and beautiful library with very simple and clean code. I personally like its source code structure very much, so here it is:

  • api
    • Getconfig. ts gets the configuration information and exports the configured data in the code
    • GetSession. Ts for the session
    • Init. ts initializes the configuration to read both the session and session expiration times in the storage
    • Login.ts calls sessionManager.main() directly
    • Request. Ts calls requesthandler. request(obj)
    • SetSession. Ts Direct setSession(internal take over, not recommended, may be a user between two small programs special needs)
    • Uploadfile. ts Uploads a file
  • module
    • The Cachemanager. ts API provides cache management based on apis and parameters. The cachemanager. ts API has no expiration time and is only suitable for unchanged data
    • Catchhandler. ts Exception handling
    • DurationReporter. Ts Time report
    • Errorhandler. ts Indicates the error handling
    • Mockmanager.ts Mock data interface
    • Requesthandler. ts request processing, formatting, uploading files, etc
    • Responsehandle. ts Response processing, from request, etc
    • Sessinmanager. ts session management, for login state control, setting and deletion
  • store
    • Config. ts default configuration, init will use object. assign to override the default configuration
    • Status. ts session, set from storage during init
  • Typings The.d.ts interface for applets
  • util
    • Loading. Ts Displays the loading configuration in the request
    • Url.ts constructs the GET request URL from the incoming object
  • Index. ts Export all apis
  • Interface. The ts weRequest interface
  • Version. ts Indicates the version information

digression

It’s hard to figure out what a business needs first, and only know what it needs most once it’s there. The same goes for business code.

And weRequest isn’t a panacea. It fits the needs of the masses, but not necessarily every business. You can also adapt and even improve the code.

To encourage the

If you think this article is good, I hope you can give me some encouragement and help me star under my Github blog. Blog address

reference

How does weRequest elegantly use Async and Await for error handling in Javascript? The async/await syntax that never uses a try-catch implementation says error handling axios requests times out, setting up a perfect solution to rerequest