0%

关于使用 handsontable 后 promise.finally 失效这件事

问题记录

  1. 旧项目使用了 handsontable 出现样式问题,试图解决问题并给 handsontable 小版本升级一下(4、5年前的项目)
  2. 升级过程中出现新问题,原有的 promise.finally polyfill 在某些页面不起作用,正常保存 promise...then().finally is not a function

定位过程

  1. 首先目的是小版本升级一下 handsontable,并在此过程中解决样式问题,原先版本为 0.31.2, 4 年前发布,考虑升级至最小且有官方文档的 4.0.0 版本
  2. 升级前没有 promise.finally is not a function 问题
  3. 查看项目代码,发现有配置 babel-polyfill 且在 main.js 有实现 promise.finally() 方法的 polyfill
  4. 考虑可能是 polyfill 方法功能不足,使用 promise.prototype.finally 库代替,并正确引入项目,在出现 is not a function 的页面引入( require('promise.prototype.finally').shim() )
  5. 此时项目运行正常,但不久后出现新的问题:其他部分页面在刷新后仍有相同报错,依次引入可解决,但不可取
  6. 与此同时,提交后发布到测试环境时,出现问题,项目启动后报错 cannot find module es-abstract...,无法打开项目,但本地环境正常
  7. 经排查发现 webpackpromise.prototype.finally 都有引用到 es-abstract,猜测版本不兼容,降低版本后,项目正常启动
  8. 令外,项目的发版已配置环境自动安装依赖,且环境与管理工具为 node8 和 yarn,而本地环境为 node10,所以本地环境正常,测试环境报错
  9. 同步环境后,问题尚未解决,消失已久的 promise.finally is not a function 再次出现
  10. 总结可能导致出现问题的点
  • axios:
    项目使用 axios 处理网络请求,并增加拦截器;发现手动 new 的 promise 实例上有 polyfill 后的 finally 方法,而 this.$http.get 返回的 promise 对象上没有
  • 新版本的 handsontable 增加了与 promise 相关的处理
  1. 首先排除一个正确答案,先把 axios 解决掉。经过多次尝试,axios 返回的 promise 对象在其他页面正常使用 finally 方法,而在使用 handsontable 的页面返回的 promise 对象与它们不同,是经过复写的,那么问题的关键就在于 handsontable 对 promise 改写了什么
    1

  2. 源码如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    Internal = function Promise(executor) {
    this._c = []; // <- awaiting reactions
    this._a = undefined; // <- checked in isUnhandled reactions
    this._s = 0; // <- state
    this._d = false; // <- done
    this._v = undefined; // <- value
    this._h = 0; // <- rejection state, 0 - default, 1 - handled, 2 - unhandled
    this._n = false; // <- notify
    };
    Internal.prototype = __webpack_require__(62)($Promise.prototype, {
    then: function then(onFulfilled, onRejected) {
    var reaction = newPromiseCapability(speciesConstructor(this, $Promise));
    reaction.ok = typeof onFulfilled == 'function' ? onFulfilled : true;
    reaction.fail = typeof onRejected == 'function' && onRejected;
    reaction.domain = isNode ? process.domain : undefined;
    this._c.push(reaction);
    if (this._a) this._a.push(reaction);
    if (this._s) notify(this, false);
    return reaction.promise;
    },
    'catch': function (onRejected) {
    return this.then(undefined, onRejected);
    }
    })

答案已经很明显了,handsontable 对全局对象 Promise 构造函数进行了重写,引用 handsontable 的组件实例当中,所使用的 promise 实例都是基于这个重写后的 Promise 构造函数创建的,所以,其他页面正常;

并且在 使用 promise.prototype.finally 之后这个组件实例上用到的 promise 则是被 此shim 覆盖后的所以正常调用 finally 方法

  1. 那么怎么解决这个问题呢,每个页面都通过 shim 覆盖一次,显然不够优雅,关键还是回到 handsontable 本身来:

    一是改写源码当中对 promise 的处理,增加 finally 等常用方法;

    二是考虑项目当中对它的调用方式,如果在源头把 handsontable 对 promise 的处理纠正回我们需要的那个 promise,那么每次创建 handsontable 实例的时候就会自动纠正,而不需要我们在每个页面手动进行

  2. 由于到此为止使用的 handsontable 版本还是 4.0.0,也就是 4 年前的,我想最新的版本肯定会增加对 已加入 promise 标准的这几个方法的处理,果然与我所想相差无几,在最新的 12.1.2 版本上,Promise.allraceresolverejectcatch 都有了,但唯独没有 finally 方法,猜对了,但没完全对

  3. 没办法,那就回到第二步,在调用 handsontable 的源头统一处理。通过一个自定义的组件来创建 handsontable 实例,并传入配置项,这个组件就是最好的解决问题的地方,和预期相同,通过覆盖 handsontable 的 Promise,解决了问题,不过没有使用 promise.prototype.finally 这个库,而是发现项目当中有使用到一个较为老版本的 bluebird.core.js,同样是基于 promise 的库,但更加注重新功能和性能,在老项目当中使用还是很好的体验

-------------本文结束感谢您的阅读-------------