2022年,才知道 request 的覆写操作,晚了嘛?

40

☀️ 前言

微信小程序的项目接入监控,由于引入了第三方组件库,导致无法监控到组件库的请求,但部分组件库的 wx.request 请求又需要被监控,以便于bug定位(方便把锅甩了😏),如何解决?而且令头秃更近一步的是 request 的方法是只读属性不能直接赋值。

image.png

尝试直接赋值,啪啪打脸秒来了。

image.png

这时候想起了先做个好宝宝,乖乖查一下 request 属性的描述信息吧。

image.png

由此可以看到request是只读属性,因此直接赋值是不被允许。那是不是意味着对于目前的场景就完全解决不了呢?需求不停代码不止,微信为了给我们程序猿们留下可扩展的空间 requestconfigurable: true,也就给覆写留了一个小小的口子,啥也不说了开干吧。

🤔 使用的奇巧淫技

当对象的属性的 configurable 键值为 true 时,是可以对属性的描述进行再次 config 的。而 ES5 提供了 Object.definePropertyObject.defineProperties 这两个方法对属性进行设置。这两个方法的作用都是在一个对象上定义新的属性或修改现有属性,并返回该对象。区别就是批量操作配置和单一配置。

image.png

关于 Object.definePropertyObject.defineProperties 这两个方法详细介绍可以参看 MDN的介绍。

🔥 覆写开干

既然奇巧淫技有了那咱干就完了,我以为的💐就要来了,完美的写出了第一版代码。

function hookRequest (options: WechatMiniprogram.RequestOption) {
  const hookOptions = {
    success: function (res: any) {
      options.success?.(res);
      console.log(res, options.url, 'request success');
    },
    fail: function (res: any) {
      options.fail?.(res);
      console.log(res, 'request fail');
    },
  };
  const requestOptions = Object.assign(options, hookOptions);
  return wx.request(requestOptions);
};

Object.defineProperty(wx, 'request', {
  value: hookRequest,
});

理想是丰满的,现实是骨感的,我被现实响亮了打了几个大嘴巴子,你以为的并不是你以为的。

image.png

image.png

这鲜亮的红色总是这么醒目且刺眼,这兜底的页面就是这么直接的出现了,现实一个绝情脚让我明白了写代码不能想象,踏实搞吧。

const originRequest = wx.request;
function hookRequest (options: WechatMiniprogram.RequestOption) {
  const originOptions = options;
  const { success, fail } = originOptions;
  const hookOptions = {
    success: function (res: any) {
      success?.(res);
      console.log(res, options.url, 'request success');
    },
    fail: function (res: any) {
      fail?.(res);
      console.log(res, 'request fail');
    },
  };
  const requestOptions = Object.assign({}, originOptions, hookOptions);
  return originRequest(requestOptions);
};

Object.defineProperty(wx, 'request', {
  value: hookRequest,
});

果然剔除想象的代码是可以正常运行的。

image.png

❓ 解决拦路虎

代码跑起来了,现在我们来分析一下刚刚我们想象中的代码为什么跪了。这里面一共出现了两个问题:

  1. RangeError: Maximum call stack size exceeded 的报错,理论上代码层面没有递归调用不应该有堆栈溢出的问题,但现在有报错,说明哪里出现了递归。
  2. 出现了死循环, successfail 函数被不停的调用了,说明哪里出现了死循环。

针对第一个问题我们看一下代码,在执行了 Object.defineProperty 时,request 方法实际已经是 hookRequest 了,但是在 hookRequest 执行 wx.request 实际执行的是自身即是 hookRequest 这也就形成递归了也就造成了溢出。因此这里需要在 defineProperty 前暂存一下 wx.request 然后再进行调用。

const originRequest = wx.request;
function hookRequest (options: WechatMiniprogram.RequestOption) {
  return originRequest(options);
}
Object.defineProperty(wx, 'request', {
  value: hookRequest,
});

由此就可以解决 RangeError: Maximum call stack size exceeded 的问题。

针对第二个问题我们看一下代码,代码中执行 const requestOptions = Object.assign(options, hookOptions); 这时实际我们已经修改了 optionssuccessfail 函数值,因此在 successfail 函数中调用 options.success?.(res);options.fail?.(res); 时实际是调用修改后的 successfail 方法,这也造成了死循环的出现。因此需要 successfail 调用原来的 options 上的方法即可,最简单的方式就是不要修改 options 的内容,对于 requestOptions 可以使用 const requestOptions = Object.assign({}, options, hookOptions);

const originRequest = wx.request;
function hookRequest (options: WechatMiniprogram.RequestOption) {
  const hookOptions = {
    success: (res: any) => {
      options.cuccess?(res);
    },
    fail: (res: any) => {
      options.fail?(res);
    },
  };
  const requestOptions = Object.assign({}, options, hookOptions);
  return originRequest(requestOptions);
}
Object.defineProperty(wx, 'request', {
  value: hookRequest,
});

由此也就解决掉死循环的问题了,对于 Object.assign 详细介绍可以看一下 MDN

👋 写在最后

  • 虽然这是一个特定场景遇到的问题,但是解决问题的过程我也变强了,这里记录一下分享给大家。
  • 如果觉得这篇文章还不错的话不妨🍉🍉关注+点赞+收藏+评论+转发🍉🍉支持一下码字的不易。