preface

Small program in the internal test of the time has started to play, but at the beginning of the feel, this SX things how so pit appearance, The network request can not return the Promise but use the Callback method, the value can not write the value in the method can only use the dataset, in this comprehensive componentization environment does not support componentization.

In fact, the beginning is mainly writing habits of the problem, adhering to the I do not do small program development, first endure your attitude laissez-faire. However, there are unexpected days, recently because of the business needs to do small program related development, I am stubborn and decisive can not endure. I resolutely changed the unhappy places into the way I liked to walk. I also encountered some other holes, and filled them in slowly one by one. I also recorded these into an article.

Network request

Web request applets provide wx.request, this is the point I most want to ridicule, take a close look at the API, this product is not $. Ajax years ago, it is very old.

// Official example
wx.request({
  url: 'test.php'.// This is an example, not a real interface address
  data: {
     x: ' ' ,
     y: ' '
  },
  header: {
      'content-type': 'application/json' / / the default value
  },
  success: function(res) {
    console.log(res.data)
  }
})
Copy the code

What if a page needs multiple requests? What if the order of requests has requirements? What if the various nesting is smelly and long?

This time I weakly looked at the JS version of the small program support, oh yeah, more conscience support ES6. So it’s possible that we could change it to a Promise. Now let’s see how I can transform it.

/* utils/api.js custom network request */
const baseURL = 'https://yourapi.com' // Address of the backend API
const http = ({ url = ' ', params = {}, ... other} = {}) = > {
  wx.showLoading({
    title: 'Loading... '
  })
  let time = Date.now()
  console.log(` begins:${time}`)
  return new Promise((resolve, reject) = > {
    wx.request({
      url: getUrl(url),
      data: params,
      header: getHeader(), ... other,complete: (res) = > {
        wx.hideLoading()
        console.log(` time-consuming:The ${Date.now() - time}`)
        if (res.statusCode >= 200 && res.statusCode < 300) {
          resolve(res.data)
        } else {
          reject(res)
        }
      }
    })
  })
}
const getUrl = url= > {
  if (url.indexOf(': / /') = =- 1) {
    url = baseURL + url
  }
  return url
}
const getHeader = (a)= > {
  try {
    var token = wx.getStorageSync('token')
    if (token) {
      return { 'token': token }
    }
    return{}}catch (e) {
    return{}}}module.exports = {
  baseURL,
  get (url, params = {}) {
    return http({
      url,
      params
    })
  },
  post(url, params = {}) {
    return http({
      url,
      params,
      method: 'post'
    })
  },
  put(url, params = {}) {
    return http({
      url,
      params,
      method: 'put'})},// Delete cannot be used here. Delete is the key field
  myDelete(url, params = {}) {
    return http({
      url,
      params,
      method: 'delete'}}})Copy the code

And then we can use it normally, so let me write a simple example

const api = require('.. /.. /utils/api.js')

// Single request
api.get('list').then(res= > {
  console.log(res)
}).catch(e= > {
  console.log(e)
})

// Multiple requests per page
Promise.all([
  api.get('list'),
  api.get(`detail/${id}`)
]).then(result= > {
  console.log(result)
}).catch(e= > {
  console.log(e)
})
Copy the code

Those of you who are used to fetch and Axios will probably like this. When making network requests, there is also a problem with login authorization.

The login problem

If you’re building an app, you can’t avoid logging in. The user’s personal information ah, the relevant collection list and other functions need to be operated after the user login. Generally, we use token for identification. Then, the token does not exist, the user logs in for the first time, and the token expires. It is more common to jump directly to the login page.

Then the pit appears. The applet does not have a login interface, but uses wx.login. Wx. login will get a code, and the backend will finally return a token to the applet, saving this value as token and carrying this value with each request. (See the applet login for details.)

But is that enough? Apparently not. Generally, it is also necessary to bring the user’s information, such as the user’s wechat nickname, wechat avatar, etc., at this time, it is necessary to use wx.getUserInfo, which involves a user authorization problem, leaving a hole to be solved later. Is it enough to bring the user information? Too young Too simple! Our project can not only have small programs, the corresponding wechat public platform may also have corresponding App, we need to open up the account system, so that users in our project account is the same. This requires the UnionID provided by wechat open platform.

Ps. Based on the spread of small programs in wechat, in order to encourage users to spread and share, an invitation reward mechanism is generally provided. But wechat side will be induced to share harmonious treatment. Use with caution according to the situation. (This article will add this feature to the examples.)

See this, is not feel the head are big, a small login function pit so much. Young I shivered ~~~. Start filling it out slowly. Start with the login code

/* utils/api.js custom network request */. function login() {return new Promise((resolve, reject) = > {
    // Call wx.login to get the code
    wx.login({
      success: res= > {
        // Call wx.getUserInfo to get some information about the user (some basic information, and the information used to generate the UnionID such as rawData, signature, encryptedData, iv)
        wx.getUserInfo({
          // If the user information is not obtained (the most possible reason is that the user authorization is not allowed, or the network request may fail, but this situation is rare)
          fail: (e) = > {
            reject(e)
          },
          success: ({ rawData, signature, encryptedData, iv }) = > {
            let param = {
              code: res.code,
              rawData,
              signature,
              encryptedData,
              iv
            }
            // If there is an invitation ID
            try {
              let invite = wx.getStorageSync('invite')
              if (invite) {
                param.invite = invite
              }
            } catch (e) {
            }
            // Login operation
            http({
              url: 'login'.params: param,
              method: 'post'
            }).then(res= > {
              If code > 0, the login is successful. All other cases are exceptions (depending on their own conditions).
              if (res.code > 0) {
                // Save the user information
                 wx.setStorage({
                  key: 'userinfo'.data: res.data
                })
                wx.setStorage({
                  key: "token".data: res.message,
                  success: (a)= > {
                    resolve(res)
                  }
                })
              } else {
                reject(res)
              }
            }).catch(error= > reject(error))
          }
        })
      }
    })
  })
}
...
Copy the code

Authorization problem

Based on the above code, it is clear that if the user does not allow the applet to access his user information when logging in, he can continue. What happens if the user clicks “no” at this point? A blank! ~ ~ What ‘s the fuck! There’s nothing there! Garbage broken small procedures ~~ calm point of the user will be 100 face meng force. Who am I? What should I do? You might think, “Well, the user can just click” allow “. How can you be so stupid? There won’t be many users. I added statistics to our mini-program that about 20% of users had clicked “no”, and if we hadn’t done anything later, those 20% would have been lost forever. This is a consequence we cannot accept at all.

After our group research and discussion, a set of schemes are given below.

The specific code can be shown as follows, using wx.openSetting to jump to the setting authorization interface.

Page({onLoad () {wx.getStorage({key: 'userinfo', success: (res) => { this.setUserinfo(res) }, fail: (res) => { api.login().then((res) => { this.setUserinfo(res) }).catch(e => { if (e.errMsg && e.errMsg === 'getUserInfo:fail auth deny') { this.setData({ isauth: false }) } }) } }) }, toSetting() { wx.openSetting({ success: (res) => { this.setData({ isauth: res.authSetting['scope.userInfo'] }) if (res.authSetting['scope.userInfo']) { api.login().then((res) => { /* index. WXML */ <view class="unauth" wx:if="{{! isauth}}"> <image class="unauth-img" src=".. /.. /images/auth.png"></image> <text class="unauth-text"> </text> <button class="color-button unauth-button" </button> </view> <view class="container" wx:else>... </view>Copy the code

Invalid Token

The token obtained through login has a validity period. What will happen if it expires? If the API is designed strictly according to the REST API specification, it will return an error with HTTP code 401. (The common Http Code and the meaning of the relevant Code will not be expanded in this article, do not know the partners can Google baidu.) After 401 we need to process this Code accordingly. We could write it like this

api.get('list').then(res= > {
  /* do something */
}).catch(e= > {
  if (res.statusCode === 401) {
    api.login().then((a)= > {
      api.get('list').then(res= > {
        /* do something */})})}})Copy the code

It looks like there’s no problem, and we’ve completed the requirements. But it turns out to be very problematic.

  1. Every request requires a 401 judgment, and the amount of code in this area is terrifying as the project grows
  2. Subsequent processing of the interface return/* do something */Also repeated (of course, the whole block of content is extracted, here is also called. But I want to omit the call side as well ^-^)

Repeat what we want to achieve.

  1. A 401 judgment needs to be appended to each request
  2. If not, log in again
  3. Continue with the previous request after re-logging in
  4. Return the result of the request to the result of the first request.

This shows the benefits of wrapping itself as a network request, so we can override the error handling in the HTTP method in api.js. The code:

const http = ({ url = ' ', params = {}, ... other} = {}) = > {
  wx.showLoading({
    title: 'Loading... '
  })
  let time = Date.now()
  console.log(` begins:${time}`)
  return new Promise((resolve, reject) = > {
    wx.request({
      url: getUrl(url),
      data: params,
      header: getHeader(), ... other,complete: (res) = > {
        wx.hideLoading()
        console.log(` time-consuming:The ${Date.now() - time}`)
        if (res.statusCode >= 200 && res.statusCode < 300) {
          resolve(res.data)
        } else if (res.statusCode === 401) {
          // 401 indicates authentication failure most likely token expiration
          // Log in again and repeat the request
          login().then(res= >{ http({ url, params, ... other }).then(res= > {
              resolve(res)
            })
          })
        } else {
          reject(res)
        }
      }
    })
  })
}
Copy the code

summary

The network request is an essential part of the current development project. However, for example, small programs, Vue, React, WEEx and so on have their own or recommended a set of APIS and corresponding writing methods. Not all of them are written according to his recommendations, which is actually quite painful and uncomfortable to use. Encapsulate their API and expose the unified API. It is more convenient to use it for myself or especially for my team partners, which reduces the cost of repeated learning. Moreover, the unified format brought by the unified API is also a great benefit.

There’s a lot to figure out when it comes to small programs, some of which I’m still figuring out how to handle. For example, the componentization of small programs, the use of global variables (what values can be put in app.js), the conversion of HTML tags, etc., I will make a fool of myself after I get through it.