Proxy来做懒惰的Promise

4,859 阅读3分钟

背景

众所周知es6为我们带来了元操作界新一任大佬——Proxy

与Object.defineProperty相比的优点:

  • 初始量少
  • 数组可用
  • 字段增删敏感
  • 多种拦截

对于proxy还不是很了解的同学可以参考以下链接瞅瞅

Proxy-MDN

Proxy-ECMAScript 6 入门

懒惰的Promise

接下来我们找点栗子来实现下这个懒惰的Promise。

微信生态今时今日还是有很多人默默地用着回调,控制不好的话,很容易就会掉进地狱回调,也用不了真香async。

虽然现在也出了不少方案来实现,但是这次就让我们忘记它,用我们的方式去实现

我们主要目标是为这位不会化妆的handle打扮成一线潮流的promise

wx.showToast({
  title: '成功',
  success () {
  	console.log('success')
  },
  fail () {
  	console.log('fail')
  }
})
// 这里把wx的api代理到wxq变量
wxq.showToast({title: '成功'})
  .then(() => console.log('success'))
  .catch(() => console.log('fail'))
  .finally(() => console.log('finally'))

单个promise

基础一点的直接封装一个函数,然后每次使用就放一个api进去,但是使用的时候,没有美感,操作变形,多了一层壳

function wxPromise(fn, param){
  return new Promise((resolve, reject) => {
    const data = Object.assign({
      success(res) {
        resolve(res)
      },
      fail(res){
        reject(res)
      }
    }, param)
    fn(data);
  })
}
wxPromise(wxq.showToast, {title: '成功'})
  .then(() => console.log('success'))
  .catch(() => console.log('fail'))
  .finally(() => console.log('finally'))

Proxy方式

在写之前,我们假设下,如果下次再碰到类似的场景。换成了uni-app或者其他一些类似的框架,我们还可以继续复用部分代码吗?应该如何去设计这份代码呢

所以在下决定将代码分成3部分

  • proxy生成函数:根据promise模板函数来代理
  • promise模板函数:根据环境来补充
  • 出口函数:屏蔽掉一些生成的细节操作
// proxy生成函数
function createProxyAll (obj, fn) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      // 操作receiver会循环
      if (!target[key]) target[key] = fn(key)
      return Reflect.get(target, key, receiver);
    }
  })
}
// promise模板函数
function wxPromise(key){
  // 缓存留key生成一个promise闭包
  return function (param) {
    return new Promise((resolve, reject) => {
      const data = Object.assign({
        success(res) {
          resolve(res)
        },
        fail(res){
          reject(res)
        }
      }, param)
      wx[key](data);
    })
  }
}
// 出口函数
function wxPromiseAll () {
  return createProxyAll(wx, wxPromise)
}

优雅降级

好东西,或多或少都会不被世人理解。在下之前就用过这个方案,风和日丽的那天,运营急冲冲告诉我有位客户的手机打开是全空白的,26°气温下我微微出了几珠汗。

while(true){思考}.png

排查到是proxy的兼容问题。

  • 那简单了,让客户换台手机(驳回)。
  • 垫片库?proxy和Object.defineProperty这种元操作是无法垫片的,环境不支持就是不支持。这也就是vue2和vue3无法为低版本浏览器提供兼容的直接原因(×)
  • 用defineProperty做降级√
function make (obj, fn) {
  // 判断Proxy,是否需要降级
  if (typeof Proxy === 'function') {
    return createProxyAll(obj, fn)
  } else {
    return createdefinePropertyAll(obj, fn)
  }
}
function createdefinePropertyAll (obj, fn) {
  const _obj = {}
  const result = {}
  Object.keys(obj).forEach(key => {
    Object.defineProperty(result, key, {
      get() {
      	// 这里的判断和proxy一样,防止引用循环
        if (!_obj[key]) _obj[key] = fn(key)
        return _obj[key]
      },
    })
  })
  return result
}

defineProperty版本实现大同小异,而且从中可以看出defineProperty版本初始化的时候就要把所有的字段都直接代理,这也是使用proxy实现的vue3的性能比使用defineProperty实现的vue2好的原因。同样,为什么我会说这个是懒惰的Promise,只有当你用到的时候,才会去进行拦截操作更改成Promise,而不用一股脑的初始化

剧终

代码可以点这里看哦

多想拿起手机,就看到你们的评论,比女神都好使