Applets network components

RequestTask wx.request(Object object)

RequestTask instructions

methods instructions
RequestTask.abort() Interrupt the request task.
RequestTask.onHeadersReceived(function callback) Listen for HTTP Response Header events. Will be earlier than the request completion event.
RequestTask.offHeadersReceived(function callback) Cancel listening for HTTP Response Header events.
RequestTask.onChunkReceived(function callback) Listen for transfer-Encoding Chunk Received events. Triggered when a new chunk is received.
RequestTask.offChunkReceived(function callback) Cancel listening for transfer-Encoding Chunk Received events.

Wx. Request (Object Object) properties

Only common attributes are listed here. You can view all attributeslink.

attribute type The default value mandatory instructions
url string is Developer server interface address
data string/object/ArrayBuffer no Requested parameters
header Object no Set the requested header. Referer cannot be set in the header.content-typeThe default isapplication/json
timeout number no Timeout duration, in milliseconds
method string GET no HTTP request methods
success function no The interface called the callback function successfully
fail function no Interface failed to invoke the callback function
complete function no This interface calls the abort callback function, even if it aborts the request.

To summarize: All applets have basically two characteristics:

  1. Each argument is an object. Easy to remember and easy to expand.

  2. They all have the same result handling: they all have the success, fail, and complete callback properties.

An introduction to errMsg objects in various cases of interface execution.

The callback property ErrMsg object
success {errMsg:”request:ok”… }
fail {errMsg:”request:fail “… } Some systems have a space after fail, so to use this judgment, it is best to use regular expressions. IndexOf greater than -1 can also be used.
abort {errMsg:”request:fail abort”… }

The sample code

  let reqTask = wx.request({
      url: getApp().globalData.api,
      success(res) {
        if (res.errMsg === "request:ok") console.log("res", res);
      },
      fail(err) {
        // if(err.errMsg.indexOf('request:fail')>-1) console.log('err', err);
        if (/^request:fail/i.test(err.errMsg)) console.log("err", err);
      },
      complete(res) {
        console.log("resOrErr", res); }});const reqTaskOnHeadersReceived = (headers) = > {
      reqTask.offHeadersReceived(reqTaskOnHeadersReceived);
      console.log("headers", headers);
      // Since the request is not completely finished, we cannot get the status code of the request, but we can determine by the length of the returned requestBody.
      // Two points: 1. Two ~~ can quickly convert string digits into digits.
      // 2. The value is less than 19 because the content-Length is "18" when the background returns the requestBody with no permission. Normally, the content-Length is greater than 19. So how much depends on the situation.
      if (~~headers.header["Content-length"] < 19) reqTask.abort();
    };
    reqTask.onHeadersReceived(reqTaskOnHeadersReceived);
Copy the code

Applets login interface

  1. wx.getUserProfile(Object object)

    Get user information. The page is invoked after a click event (such as in the bindtap callback on a Button), and an authorization window pops up for each request, which returns userInfo after the user agrees. This interface is used to replace wx.getUserInfo. For details, see User Information Interface Adjustment.

  2. wx.checkSession(Object object)

    Check whether the login status has expired. The login status obtained through the Wx. login interface has a certain timeliness. The longer the user does not use the applets, the more likely it is that the user login state will fail. Otherwise, if the user has been using the mini program, the user login status remains valid. The specific timing logic is maintained by wechat and transparent to developers. The developer only needs to call the wx.checkSession interface to check whether the current login status of the user is valid.

    After the login state expires, the developer can call wx.login to obtain a new user login state. If the call succeeds, the current session_key is not expired. If the call fails, the current session_key is expired. See applet Login for more details.

  3. wx.login(Object object)

    Call the interface to get the login credentials (code). Exchange user login status information through credentials, including the unique identifier of the user in the current applets (OpenID), the unique identifier under the wechat open platform account (unionID, if the current applets have been bound to the wechat open platform account) and the session key (session_key) of this login, etc. The encryption and decryption communication of user data depends on session key. See applet Login for more details.

Back-end login interface code implementation

The backend uses NodeJS, KOA ^2.13.4, routing framework @koa/ Router ^10.1.1, Framework Request ^2.88.2, jsonWebToken to encrypt and decrypt token information, version ^8.5.1

// app.js
const Koa = require("koa");
const Router = require("@koa/router");
const WeixinAuth = require("./lib/koa2-weixin-auth");
const jsonwebtoken = require("jsonwebtoken");

const app = new Koa();
// Small program ticket information
const miniProgramAppId = "* * * * * * * * *";
const miniProgramAppSecret = "* * * * * * * * * * *";
const weixinAuth = new WeixinAuth(miniProgramAppId, miniProgramAppSecret);

const JWT_SECRET = "JWTSECRET";
@koa/router is required for the routing middleware
// Enable a route with a group
const router = new Router({
  prefix: "/user"});// This is the normal login method
// Add a parameter, sessionKeyIsValid, to indicate whether the sessionKey is still valid
router.post("/weixin-login".async (ctx) => {
  let { code, userInfo, encryptedData, iv, sessionKeyIsValid } =
    ctx.request.body;
   / / parsing openid
  const token = await weixinAuth.getAccessToken(code);
  userInfo.openid = token.data.openid;
  // It can be handled by itself, such as logging to the database, handling tokens, etc
  let authorizationToken = jsonwebtoken.sign(
    { name: userInfo.nickName },
    JWT_SECRET,
    { expiresIn: "1d"});Object.assign(userInfo, { authorizationToken });
  ctx.status = 200;
  ctx.body = {
    code: 200.msg: "ok".data: userInfo,
  };
});
Copy the code
// lib/koa2-weixin-auth.js
const querystring = require("querystring");
const request = require("request");

const AccessToken = function (data) {
  if(! (this instanceof AccessToken)) {
    return new AccessToken(data);
  }
  this.data = data;
};

/ *! * check whether AccessToken isValid by comparing the current time with the expiration date * * Examples: * ' '* token.isvalid (); * ` ` ` * /
AccessToken.prototype.isValid = function () {
  return(!!!!!this.data.session_key &&
    new Date().getTime() < this.data.create_at + this.data.expires_in * 1000
  );
};

/** * Create OAuth interface constructors based on appID and appSecret * Access tokens need to be maintained globally if needed to operate across processes and machines * Use the priority of using tokens is: ** 1. Use the currently cached token object * 2. Call the asynchronous method that is passed in to get the token and use it (and cache it) after the token is obtained. * Examples: * ``` * var OAuth = require('oauth'); * var api = new OAuth('appid', 'secret'); * ` ` ` *@param {String} Appid Indicates the appID * applied for on the public platform@param {String} Appsecret App secret */ obtained through application on public platform
const Auth = function (appid, appsecret) {
  this.appid = appid;
  this.appsecret = appsecret;
  this.store = {};

  this.getToken = function (openid) {
    return this.store[openid];
  };

  this.saveToken = function (openid, token) {
    this.store[openid] = token;
  };
};

/** * Get the URL of the authorization page *@param {String} Redirect Indicates the address to redirect after authorization *@param {String} State Data available to the developer *@param {String} Scope Specifies the scope. The value is snsapi_userinfo and snsapi_base. The former is used to eject and the latter is used to jump */
Auth.prototype.getAuthorizeURL = function (redirect_uri, scope, state) {
  return new Promise((resolve, reject) = > {
    const url = "https://open.weixin.qq.com/connect/oauth2/authorize";
    let info = {
      appid: this.appid,
      redirect_uri: redirect_uri,
      scope: scope || "snsapi_base".state: state || "".response_type: "code"}; resolve(url +"?" + querystring.stringify(info) + "#wechat_redirect");
  });
};

/ *! * Handle tokens, update expiration time */
Auth.prototype.processToken = function (data) {
  data.create_at = new Date().getTime();
  / / store token
  this.saveToken(data.openid, data);
  return AccessToken(data);
};

/** * After obtaining the OpenID, you can call 'wechat.API' to obtain more information *@param {String} Code Indicates the code */ obtained by authorization
Auth.prototype.getAccessToken = function (code) {
  return new Promise((resolve, reject) = > {
    const url = "https://api.weixin.qq.com/sns/jscode2session";
    // Because the framework version has not been updated for a long time, the address here has changed, need to change to the above address, otherwise it will appear
    / / 41008 error. This is why there is no direct use of the framework, referencing native use.
    // const url = "https://api.weixin.qq.com/sns/oauth2/access_token";
    const info = {
      appid: this.appid,
      secret: this.appsecret,
      js_code: code,
      grant_type: "authorization_code"}; request.post(url, {form: info }, (err, res, body) = > {
      if (err) {
        reject(err);
      } else {
        const data = JSON.parse(body);
        resolve(this.processToken(data)); }}); }); };/** * Refresh access token according to refresh token, only valid after call getAccessToken *@param {String} refreshToken refreshToken
 */
Auth.prototype.refreshAccessToken = function (refreshToken) {
  return new Promise((resolve, reject) = > {
    const url = "https://api.weixin.qq.com/sns/oauth2/refresh_token";
    var info = {
      appid: this.appid,
      grant_type: "refresh_token".refresh_token: refreshToken,
    };
    request.post(url, { form: info }, (err, res, body) = > {
      if (err) {
        reject(err);
      } else {
        const data = JSON.parse(body);
        resolve(this.processToken(data)); }}); }); };/** * Obtain user information according to openID. * When an Access token is invalid, the system automatically obtains a new Access token through refresh token. Then get the user information *@param {Object|String} Options Pass in the openID or see options */
Auth.prototype.getUser = async function (openid) {
  const data = this.getToken(openid);
  console.log("getUser", data);
  if(! data) {var error = new Error(
      "No token for " + options.openid + ", please authorize first."
    );
    error.name = "NoOAuthTokenError";
    throw error;
  }
  const token = AccessToken(data);
  var accessToken;
  if (token.isValid()) {
    accessToken = token.data.session_key;
  } else {
    var newToken = await this.refreshAccessToken(token.data.refresh_token);
    accessToken = newToken.data.session_key;
  }
  console.log("accessToken", accessToken);
  return await this._getUser(openid, accessToken);
};

Auth.prototype._getUser = function (openid, accessToken, lang) {
  return new Promise((resolve, reject) = > {
    const url = "https://api.weixin.qq.com/sns/userinfo";
    const info = {
      access_token: accessToken,
      openid: openid,
      lang: lang || "zh_CN"}; request.post(url, {form: info }, (err, res, body) = > {
      if (err) {
        reject(err);
      } else {
        resolve(JSON.parse(body)); }}); }); };/** * Obtain user information according to code. *@param {String} Code Indicates the code */ obtained by authorization
Auth.prototype.getUserByCode = async function (code) {
  const token = await this.getAccessToken(code);
  return await this.getUser(token.data.openid);
};

module.exports = Auth;
Copy the code

Small program side login code implementation

<! --pages/index.wxml-->
<view class="page-section">
    <text class="page-section__title">WeChat login</text>
    <view class="btn-area">
        <button  bindtap="getUserProfile" type="primary">The login</button>
    </view>
</view>
Copy the code
// pages/index.js
Page({
  /** * initial data for the page */
  data: {},
  // The correct login method
  getUserProfile() {
    Wx.getuserprofile is recommended to obtain user information. Every time the developer obtains user information through this interface, the user must confirm
    // The developer keeps the avatar nicknames quickly filled in by the user to avoid repeated pop-ups
    wx.getUserProfile({
      desc: "To improve member information".// Specify the purpose of obtaining the user's personal information, which will be displayed in the subsequent pop-up window
      success: (res) = > {
        let { userInfo, encryptedData, iv } = res;
        const requestLoginApi = (code) = > {
          // Initiate a network request
          wx.request({
            url: "http://localhost:3000/user/weixin-login".method: "POST".header: {
              "content-type": "application/json",},data: {
              code,
              userInfo,
              encryptedData,
              iv,
            },
            success(res) {
              console.log("Request successful", res.data);
              let token = res.data.data.authorizationToken;
              wx.setStorageSync("token", token);
              onUserLogin(token);
              console.log("authorization", token);
            },
            fail(err) {
              console.log("Request exception", err); }}); };const onUserLogin = (token) = > {
          getApp().globalData.token = token;
          wx.showToast({
            title: "Login successful"}); };// The session must be checked to see if the session expires. Otherwise, the server will report "Illegal Buffer" for the first time
        // error, but the second click login normal.
        wx.checkSession({
          success: (res) = > {
            // session_key has not expired and remains valid for this life cycle
            console.log("In landing.");
            let token = wx.getStorageSync("token");
            if (token) onUserLogin(token);
          },
          fail: (res) = > {
            // Session_key has expired and the login process needs to be re-executed
            wx.login({
              success(res0) {
                if (res0.code) {
                  requestLoginApi(res0.code);
                } else {
                  console.log("Login failed!"+ res0.errMsg); }}}); }}); }}); }});Copy the code

What optimizations can be made for the login code?

For a piece of software, at the code level, there are a few basic aspects to pursue (far more than that, but let’s do these first) :

  1. Maintainability

    Maintenance is nothing more than fixing bugs, modifying old code, adding new code, and so on. “Code maintainable” is the ability to quickly change or add code without breaking the original code design or introducing new bugs. “Code is not maintainable” means that changes or additions to code run a significant risk of introducing new bugs and can take a long time to complete.

  2. Readability

    Software design guru Martin Fowler once said: “Any fool can write code that a computer can understand. Good programmers write code that humans can Understand. “Any fool can write code that a computer can understand. Good programmers write code that people understand.” Google even has an internal certification called Readability. Only engineers with this certification are qualified to approve others to submit code for code review. You can see how important the readability of code is, after all, code is read far more often than it is written and executed. We need to see if the code conforms to the code specification, if the name makes sense, if the comments are detailed, if the function length is appropriate, if the modules are clearly divided, if the code conforms to high cohesion and low coupling, and so on.

  3. Extensibility

    Extensibility is also an important criterion for evaluating code quality. There are extension points in the code, so you can plug new functionality directly into the extension point, without having to change a lot of the original code to add a feature.

  4. Reusability

    Code reusability can be simply understood as minimizing the need to write duplicate code and reusing existing code.

So let’s optimize the code:

modular

You can modularize the login code as follows:

// lib/login.js
function loginWithCallback(cb) {
  Wx.getuserprofile is recommended to obtain user information. Every time the developer obtains user information through this interface, the user must confirm
  // The developer keeps the avatar nicknames quickly filled in by the user to avoid repeated pop-ups
  wx.getUserProfile({
    desc: "To improve member information".// Specify the purpose of obtaining the user's personal information, which will be displayed in the subsequent pop-up window
    success: (res) = > {
      let { userInfo, encryptedData, iv } = res;
      const requestLoginApi = (code) = > {
        // Initiate a network request
        wx.request({
          url: "http://localhost:3000/user/weixin-login".method: "POST".header: {
            "content-type": "application/json",},data: {
            code,
            userInfo,
            encryptedData,
            iv,
          },
          success(res) {
            console.log("Request successful", res.data);
            let token = res.data.data.authorizationToken;
            wx.setStorageSync("token", token);
            onUserLogin(token);
            console.log("authorization", token);
          },
          fail(err) {
            console.log("Request exception", err); }}); };const onUserLogin = (token) = > {
        getApp().globalData.token = token;
        wx.showToast({
          title: "Login successful"});if (cb && typeof cb == "function") cb(token);
      };
      wx.checkSession({
        success: (res) = > {
          // session_key has not expired and remains valid for this life cycle
          console.log("In landing.");
          let token = wx.getStorageSync("token");
          if (token) onUserLogin(token);
        },
        fail: (res) = > {
          // Session_key has expired and the login process needs to be re-executed
          wx.login({
            success(res0) {
              if (res0.code) {
                requestLoginApi(res0.code);
              } else {
                console.log("Login failed!"+ res0.errMsg); }}}); }}); }}); }export default loginWithCallback;
Copy the code
Promise to change

The callback hell problem is bad for code reading, so let’s optimize the code based on promises. With the Promise object, asynchronous operations can be expressed as a flow of synchronous operations, avoiding layers of nested callback functions. In addition, Promise objects provide a unified interface that makes it easier to control asynchronous operations.

A brief introduction to several methods of Promise

The method name instructions
Promise.prototype.then Method returns a new Promise object, so you can write it chained. This design allows nested asynchronous operations to be easily rewritten from “horizontal development” to “downward development” of the callback function.
Promise.prototype.catch Is an alias to promise.prototype. then(null, Rejection) that specifies the callback when an error occurs. Errors in the Promise object are “bubbling” and are passed backwards until they are caught. That is, an error is always caught by the next catch statement.
Promise.prototype.finally Method returns aPromise. At the end of the promise, the specified callback function will be executed, whether the result is fulfilled or Rejected. For the inPromiseCode that needs to be executed after successful completion or not provides a way.
Promise.all This avoids the need for the same statement inthen()andcatch()In each case. The promise.all method is used to wrap multiple Promise instances into a new Promise instance. The promise. all method takes an array as an argument,var p = Promise.all([p1,p2,p3]);P1, P2, and P3 are all instances of Promise objects. (The promise. all method does not have to take an array as an argument, but it must have an iterator interface and return each member as a Promise instance.) The state of P is determined by P1, P2 and P3, which can be divided into two cases. (1) Only when the states of P1, P2 and P3 become depressing, the state of P will become depressing. At this time, the return values of P1, P2 and P3 will form an array and be passed to the callback function of P. (2) As long as p1, P2 and P3 are rejected, P becomes rejected, and the return value of the first rejected instance is passed to p’s callback function.
Promise.race The promise.race method also wraps multiple Promise instances into a new Promise instance.var p = Promise.race([p1,p2,p3]);In the above code, the state of P changes as long as one of the first instances of P1, P2, and P3 changes state. The return value of the first changed Promise instance is passed to the return value of P.
Promise.any Take a Promise iterable and return the Promise that succeeded as soon as one of the promises succeeds. All child instances are in the Rejected state, and the total promise is in the Rejected state.
Promise.allSettled Returns a promise that all given promises have beenfulfilledorrejectedAfter the promise, with an array of objects, each representing the corresponding promise result. By contrast,Promise.all()More suited to interdependence or either of themrejectImmediately end.
The apPLETS API interface promises and modularizes the call interface that requires login
  1. Install the plug-in. Check the NPM support documentation first.
npm install --save miniprogram-api-promise
Copy the code
  1. Check the use NPM module in the details on the right of wechat Developer tools, and click Build NPM in the menu bar tools.
  2. Initialize the code.
// app.js
import {promisifyAll} from 'miniprogram-api-promise'
import login from ".. /lib/login";
const wxp ={}
promisifyAll(wx,wxp)
// Requests that require tokens handle login and header Settings uniformly, and handle error messages
wxp.requestNeedLogin = async function (args) {
  let token = wx.getStorageSync("token");
  if(! token) { token =await loginWithPromise();
  }
  if(! args.header) args.header = {}; args.header["Authorization"] = `Bearer ${token}`;
  return wxp.request(args).catch(console.error);
};
// app.js
App({
  wxp:wxp,
});
Copy the code
  1. Rewrite the login.js code
// lib/login.js
function login() {
  return new Promise((resolve, reject) = > {
    Wx.getuserprofile is recommended to obtain user information. Every time the developer obtains user information through this interface, the user must confirm
    // The developer keeps the avatar nicknames quickly filled in by the user to avoid repeated pop-ups
    wx.getUserProfile({
      desc: "To improve member information".// Specify the purpose of obtaining the user's personal information, which will be displayed in the subsequent pop-up window
       success:async (res0) => {
        let {
          userInfo,
          encryptedData,
          iv
        } = res0;
        const app = getApp();
        try {
          app.wxp.checkSession();
        } catch (err) {
          reject(err);
        }
        let token = wx.getStorageSync("token");
        if(! token) {let res1 = await app.wxp.login().catch(err= > reject(err));
          let code = res1.code;
          let res = await app.wxp.request({
            url: "http://localhost:3000/user/weixin-login".method: "POST".header: {
              "content-type": "application/json",},data: {
              code,
              userInfo,
              encryptedData,
              iv,
            }
          }).catch(err= > reject(err));
          token = res.data.data.authorizationToken;
          wx.setStorageSync("token", token);
          app.globalData.token = token;
          wx.showToast({
            title: "Login successful"}); resolve(token); }}}); })}export default login;
Copy the code
  1. The calling code
<view class="container page-head">
  <text class="page-section__title">Request invocation that requires login</text>
  <view class="btn-area">
    <button bindtap="request1" type="primary">Request 1</button>
    <button bindtap="request2" type="primary">Request 2</button>
  </view>
</view>
Copy the code
// pages/index.js
Page({
  /** * initial data for the page */
  data: {},
  request1() {
    getApp().wxp.requestNeedLogin({
        url: "http://localhost:3000/user/home? name=andying",
      }).then(console.log)
  },
  request2() {
    getApp().wxp.requestNeedLogin({
        url: "http://localhost:3000/user/home? name=eva",
      }).then(console.log)
  },
});
Copy the code