At my first job, my boss asked me to encapsulate the request, and I immediately said, encapsulate what? Why to encapsulate, itself somebody else’s library has been encapsulated ah, only need a few parameters can be called, encapsulated or to pass some parameters. Well ~ at that time or a little reasonable and strong, is the so-called ignorant meaningless 😂 of course I still listen to the boss, at that time I just encapsulated a few default parameters 🐶 and then after a few years of experience, the API request has been in the upgrade, now please accompany me to review together

Why encapsulate

  • Simplify the cost of use. Unlike libraries, encapsulation is project-specific, and we can specify the primary domain name of the request, the default value of the request, and reduce the need to pass parameters and other configurations when using the request
  • Some logic can be handled uniformly, such as alert handling of request exceptions
  • The request can be modified to meet usage habits

How to encapsulate

The PROMISE of callback VS

Obviously, callbacks tend to fall into callback hell, so we recommend promise programming for requests and other scenarios, especially with the introduction of new async/await

Has always been resolve

Requests are always resolved? Why is that? If not, what happens?

async function f() {
  try {
    await Promise.reject('Wrong'); } catch(e) { ... }... }Copy the code

As in the code above, what happens if we don’t add a catch? All code following the f function will not be executed, that is to say, if we want to keep our code robust we must add try/catch tolerance to async/await functions. Then we should not use async, which is a good idea, but I must remind async of some benefits:

  • It’s useful to always return promises, for example
if (check) {
  return true
} else {
  return apiPromise()
}
Copy the code

Do you determine the type of return value or resolve True? Async is a perfect solution to this problem!

  • I’m sure you’re already using async a lot, so try/catch if you’re using async/await

Even in simple scenarios where async is not required, there are minor issues with promise rejection, such as

api().then(res=>{
  this.hideLoading()
  ...
}).catch(err=>{
  this.hideLoading()
  ...
})
Copy the code

Some code that will execute whether resolved is successful or not needs to be written in two places

So, what do you want to promote? Encapsulated API requests are always resolved, so there’s no need to care about reject? Wouldn’t it be nice not to have to worry about all the questions? 😊 wrong, there is always abnormal situation, ah, don’t care? Also resolve! 😊 add field distinction to go, be very clever? 😁

Verify that the request is perfectly correct

What do you mean? Let’s recall if we’ve ever written a lot of code like this, and if not, ignore it

api().then(res=>{
  this.hideLoading()
  if (res.code !== 0) {
    ...
  }
  ...
})
Copy the code

Because many back ends do not directly return the abnormal HTTP status code for monitoring the running status and other reasons, but use various codes to indicate whether the processing is successful, so 200 requests are not necessarily true request completion, so the verification code becomes a must

Like a real API

The API is a bit overused. The API requested by the API is the business service interface provided in the background. What is the normal API in our mind without this one? Array.push (1), like this, is a predefined function that doesn’t care about its internal implementation, so wrap the API request like a real API, easy to use and available anywhere

Now that I’ve covered my thoughts on encapsulating API requests, let’s look at the code implementation (based on applets, for your reference, the core code marked with ===============).

Source reference

Let’s see how well you use it

// curl --request POST // --url'http://user-interaction.ylf.org/controller/fun' \
//  --header 'Content-Type: application/json' \
//  --data '{
//	"page":1
// }'// You only need api.controller.fun({page: 10}).then(res=>{this.hideloaing ())if(res.errType) { ... // Exception handling}... // Async mode asyncfunction() {
  const res = await api.controller.fun({page: 10})
  this.hideLoaing()
  if(res.errType) { ... // Exception handling}... // Normal}Copy the code

The directory structure

API ├ ─ ─doRequest.js // Encapsulates the Request method ├─ index.js // generates API andexportRequest method such as ├ ─ ─ inject. Js / / interceptor ├ ─ ─ renewToken. Js / / retrieves token └ ─ ─ serviceJson. Js / / configuration for generated APICopy the code

Dorequest.js // encapsulates the request method

import _ from '.. /lib/tools'
import injectMap from './inject'
import {api as constApi} from '.. /constVar'
import renewToken from './renewToken'

const apiDomain = constApi.domain

let getTokenPromise = ' '// Only one instance can exist simultaneouslyletWxSessionValid = null // validity of wechat session_key const checkWxSession =function () {
  return new Promise(resolve => {
    wx.checkSession({
      success() {
        resolve(true) // session_key is not expired and remains valid for this life cycle},fail() {
        resolve(false) / / session_key have failed and need to perform a login process}})})} / / check whether the business layer is also successfully processed, the return value of a parameter for the request const defaultCheckDoRequestSuccess = (res) = >! res.data.error_codeexport async function doRequestWithCheckSession(data = {}, opts) {
  const opt = Object.assign({needToken: true}, opts)
  if (typeof opt.needToken === 'function') {// If authentication is required logically, the needToken configuration can be set to a function that returns a Boolean value, without opt.needToken = opt.needToken()}if(typeof wxSessionValid ! = ='boolean'{wxSessionValid = await checkWxSession()}let jwt = wx.getStorageSync('jwt') // Authentication mode: service side authentication and wechat session validity authenticationif(opt.needToken && (! jwt || jwt.expire_after <= +new Date() || ! WxSessionValid)) {// Need authorization, expired, to renew the leaselet jwt = ' '
    if (getTokenPromise) {
      jwt = await getTokenPromise
    } else {
      getTokenPromise = renewToken()
      jwt = await getTokenPromise
    }
    wxSessionValid = true
    getTokenPromise = ' '
    wx.setStorageSync('jwt', jwt)
  }
  Object.assign(opt, opt.needToken ? {httpOpt: {header: {Authorization: jwt.token}}} : {})
  return doRequest(opt.url, data, Opt)} = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = * / * * * request interface function @param URL * @param data Request body * @param opt See the parameters * @returns {Promise<any>} * *!! Always resolved, never rejected, but you can tell if OK * errType === is requested by checking if there is an errType value'http'Error request * errType ==='server'CheckDoRequestSuccess = checkDoRequestSuccess (*/)export function doRequest(url, data, {
  method = 'get', httpOpt = {}, needToken = true, needToast = true,
  checkDoRequestSuccess = defaultCheckDoRequestSuccess
} = {}) {
  returnnew Promise((resolve) => { wx.request({ url, data, method, ... HttpOpt, success: (res) => {// Request successfulifInjectmap. forEach((val, key) => {// Match the interception ruleif (key.indexOf(url.replace(apiDomain, ' ')) !== -1) {
              val()
            }
          })
          resolve(res)
        } else{/ / server processing failure needToast && wx. ShowToast ({title: res. Data. A tiny | |'Request error, please try again later',
            icon: 'none',
            duration: 2000
          })
          resolve(Object.assign({
            errType: 'server'}}}, res)), fail: (err) = > {/ / request to resolve the failure ({errType:'http'. err }) checkNetWorkAndSaveCurrentPath() } }) }) } = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = / / check the network problem and record the path of the current pagefunction checkNetWorkAndSaveCurrentPath() {/* eslint-disable no-undef */ const pages = getCurrentPages() // Get the current page stack const Page = pages[pages.length-1] // Current page // Avoid multiple request failures resulting in multiple weak page stack, affecting bounce backif (['pages/normal/network'.'pages/normal/load'].indexOf(page.route) ! = = 1) {return
  }
  wx.getNetworkType({
    success: function(res) {const pathParamsStrArr = [] // Records the path parameter of the current page _.forown (page.options, (v, k) => {pathParamSStrarr.push ('${k}=${v}`)
      })
      const path = `${page.route}?${pathParamsStrArr.join('&')}`
      wx.setStorageSync('badNetPagePath', path) // Record the complete path of the page interrupted by the weak networkif (res.networkType === 'none'Wx. RedirectTo ({url:'/pages/normal/network'})}else{// Weak network and other exceptions wx.redirectTo({url:'/pages/normal/load'})}}})}Copy the code

Core index.js // generate API and export request method etc

import serviceJson from './serviceJson'
import { doRequestWithCheckSession, doRequest } from './doRequest'
import _ from '.. /lib/tools'
import {api as constApi} from '.. /constVar'

const apiDomain = constApi.domain
const api = {}


serviceJson.forEach(obj => {
  const keys = obj.url.replace(/\//g, '. ')
  obj.url = apiDomain + obj.url
  _.set(api, keys, function (data) {
    return doRequestWithCheckSession(data, obj)})}) /** * Call example * api.controller.fun({page: 10}) ** Exposes two wrapped request methods */exportdefault { ... api,doRequest,
  doRequestWithCheckSession
}
Copy the code

Core servicejson.js // for the configuration of the generated API

/** * Project request configuration ** Parameters please go to./doRequest. Js viewdoRequest function description, the following parameters may change and cause inaccurate * needToken=trueWhether token authentication is required * method=get request method * dataType= JSON dataType * check function, parameter is the return value of the request, requires the return Boolean value,trueIndicates that the request is successful (background processing is successful),falseOn the other hand * /export default [
  {'url': 'joke/content/list'}]Copy the code

Inject.js // interceptor

// 请求hooks,当请求被匹配则执行预设的回调
// map的key为 ./serviceJson.js 配置里的url,value为callback

// import _ from '.. /lib/tools'

const map = new Map()

export default map
Copy the code

Renewtoken. js // Renew the tokens

import {doRequest} from './doRequest'
import _ from '.. /lib/tools'
import {api as constApi} from '.. /constVar'

const apiDomain = constApi.domain

functionnavToLogin(resolve) { /* eslint-disable no-undef */ const pages = getCurrentPages() const page = pages[pages.length - 1]  page.openLoginModal(resolve) }export default async function renewToken{resolve => {wx.getSetting({success:}) {wx.getSetting({success:}) {resolve => {wx.getsetting ({success:}); (res) => {// If the user is not authorized or does not have the required user informationif(! res.authSetting['scope.userInfo') | |! _.isRealTrue(wx.getStorageSync('userInfoRes').userInfo)) {
          wx.hideLoading()
          navToLogin(resolve)
        } else {
          resolve()
        }
      }
    })
  })
  returnnew Promise((resolve) => { wx.login({ success: Res => {login(res.code).then((JWT) => {resolve(JWT) // resolve JWT}) fail(err) { wx.showToast({ title: err.errMsg, icon:'none'JWT * @param code * @returns {Promise<any>} */function login(code) {
  returnNew Promise((resolve) => {// Mock login to exchange business user information and login information, test onlydoRequest(apiDomain + 'test/getToken', {code}, { needToast: false }).then(res => {
      if (res.errType) {
        // resolve('loginerr')
        resolve({
          'token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9'.'expire_after': +new Date() + 1000 * 360 * 24
        })
        return
      }
      resolve({
        'token': 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9'.'expire_after': +new Date() + 1000 * 360 * 24
      })
    })
  })
}
Copy the code

Although it is the thinking of API, not only limited to small programs, but as a thinking and summary of the same time, to wave series link 😄 development of micro channel small program must know things micro channel small program login state exploration

Welcome to exchange comments, thank you