Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

讨厌的回调 #7

Open
kangjs7854 opened this issue Aug 7, 2020 · 0 comments
Open

讨厌的回调 #7

kangjs7854 opened this issue Aug 7, 2020 · 0 comments

Comments

@kangjs7854
Copy link
Owner

kangjs7854 commented Aug 7, 2020

场景,小程序的api大多数是异步采用回调函数的方式来处理成功或者失败的结果

假如你的项目要实现一个用户登录功能

  1. 调用wx.login()
    这个api获取用户临时凭证code
  2. 调用服务端的方法,假设有一个getOpenid()的函数
    服务端会根据这个code,小程序appid和密钥,请求对应的小程序凭证验证的api,返回用户的openid和seesionkey,再将这个结果返回给小程序保存起来。
  3. 调用服务端获取自身系统用户中心的方法,假设有一个getUserInfo()的函数

服务端根据小程序传过去的openid,在自己的用户中心进行用户的注册或检索,其实可以和第二步合在一起处理。具体还是根据公司业务来。

可以看到,要获取用户信息就需要调用三个api,而微信的api全是回调!

再假如我们获取用户信息后还要保存到本地,在调用wx.login时还需要检查登录态是否过期,防止重复调用产生不同的seesion_key导致某些接口报 session_key无效的错误。这就又多了两个回调函数,不管是开发还是后期维护都非常的不方便。

 wx.login({
            timeout:10000,
            success: (res) => {
                wx.request({
                    url: '/api/getOpenid',
                    data: {code: res.code},
                    header: {'content-type':'application/json'},
                    method: 'POST',
                    dataType: 'json',
                    responseType: 'text',
                    success: (result) => {
                        wx.request({
                            url: '/api/getUserInfo',
                            data: {},
                            header: {'content-type':'application/json'},
                            method: 'GET',
                            dataType: 'json',
                            responseType: 'text',
                            success: (res) => {
                                wx.setStorage({
                                    key: 'userInfo',
                                    data: res.data,
                                    success: (result) => {
                                        
                                    },
                                    fail: () => {},
                                    complete: () => {}
                                });
                                  
                            },
                            fail: () => {},
                            complete: () => {}
                        });
                          
                    },
                    fail: () => {},
                    complete: () => {}
                });
                  
            },
            fail: () => {},
            complete: () => {}
        });


包装成promise

小程序开发者工具内置es6转es5,所以使用es6的新特性完全没问题,我们可以利用promise来解决回调函数的问题

  /**
   * 传入小程序的api,转成promise
   * 第一版,传入整个小程序的api函数
   * @param {Function} fn 小程序的api 
   * @return {Function} 返回的promise函数
   */
  promisify: (fn) => {
    return (args = {}) => {
      return new Promise((resovled, rejected) => {
        args.success = res => {
          resovled(res)
        }
        args.fail = err => {
          rejected(err)
        }
        fn(args)
      })
    }
  },

通过把需要转换的小程序api传入promisify函数,返回一个promise对象

//使用
const wxLogin = promisify(wx.login)
const wxRequest = promisify(wx.request)
const wxSetStorage = promisify(wx.setStorage)
wxLogin()
  .then(res => {
    return wxRequest({
      url: "/api/getOpenid",
      data: { code: res.code },
      header: { 'content-type': 'application/json' },
      method: 'GET',
      dataType: 'json',
      responseType: 'text',
    })
  })
  .then(res => {
    return wxRequest({
      url: "/api/getUserInfo",
      data: { openid: res.openid },
      header: { 'content-type': 'application/json' },
      method: 'GET',
      dataType: 'json',
      responseType: 'text',
    })
  })
  .then(res => {
    return wxSetStorage({
      key: 'userInfo',
      data: res.data,
    })
  })
  .then(res=>{
    //弹窗提醒 or 做啥都行
  })

这样处理之后代码的逻辑和结构是不是清晰了很多,promise真香!

代理wx这个对象的api为promise

虽然上方的处理解决了回调函数的问题,但是每次都需要调用这个函数来包装小程序的api,比较麻烦

这里就需要使用es6的proxy,也就是vue3版本代理object.defindproperty实现数据监听的新方法。
其实整个proxy人如其名,就是代理的意思,不过他比劫持数据对象的get和set更加强大,不仅仅支持对象,还能
代理数组等。欸,小程序的api不都是定义在wx这个对象里面嘛,是不是突然有了思路

通过代理wx这个对象在使用其对应的api时,包上一层promisify函数,转成promsie对象,我们不就可以实现 和上面一样的功能,甚至更加优雅。

  /**
 * 将小程序的api代理成promise形式第二版
 * @return {Object} 返回一个proxy函数,代理wx这个对象
 * 然后当这个代理对象使用小程序api时,会把原生的api转成promise对象
 * @example const WX = proxy() 
 * WX.showToast({title:'666'}).then(res=>WX.showLoading())
 */
  proxy: () => {
    function promisify(fn) {
      return (args = {}) => {
        return new Promise((resovled, rejected) => {
          wx[fn]({
            ...args,
            success: resovled,
            fail: rejected,
          })
        })
      }
    }
    return new Proxy(wx, {
      get: (target, prop) => {
        return promisify(prop)
      }
    })

  }

由于小程序没有全局变量,例如window或者global之类的,我们只能退而求其次,在需要使用到的引入,可以把需
要这个proxy函数放到app.js中方便引用

//使用
const app = getApp()

const WX = app.proxy()
WX.login()
  .then(res => {
    return WX.request({
      url: "/api/getOpenid",
      data: { code: res.code },
      header: { 'content-type': 'application/json' },
      method: 'GET',
      dataType: 'json',
      responseType: 'text',
    })
  })
  .then(res => {
    return WX.request({
      url: "/api/getUserInfo",
      data: { openid: res.openid },
      header: { 'content-type': 'application/json' },
      method: 'GET',
      dataType: 'json',
      responseType: 'text',
    })
  })
  .then(res=>{
    return WX.setStorage({
      key: 'userInfo',
      data: res.data,
    })
  })
  .then(res=>{
    //弹窗提醒 or 做啥都行
  })

是不是更加优雅了呢!

使用 async await

虽然将api转换成promise的形式解决了回调嵌套过深导致的可读性可维护性都很差的问题,但是promise链式调用导致的调试困难还是难以避免,这里由于把wx这个对象的api的回调转换成了promise,我们可以使用async和await来处理,写法更加优雅,符合开发者的阅读开发习惯。

async function handleLogin() {
    const app = getApp()
    const WX = app.proxy()
    const res = await WX.login()

    const loginIngo = await WX.request({
        url: "/api/getOpenid",
        data: { code: res.code },
        header: { 'content-type': 'application/json' },
        method: 'GET',
        dataType: 'json',
        responseType: 'text',
    })

    const userInfo = await WX.request({
        url: "/api/getUserInfo",
        data: { openid: loginIngo.openid },
        header: { 'content-type': 'application/json' },
        method: 'GET',
        dataType: 'json',
        responseType: 'text',
    })

    await WX.setStorage({
        key: 'userInfo',
        data: userInfo,
    })

}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant