问题记录
- 旧项目使用了 handsontable 出现样式问题,试图解决问题并给 handsontable 小版本升级一下(4、5年前的项目)
- 升级过程中出现新问题,原有的
promise.finally polyfill
在某些页面不起作用,正常保存promise...then().finally is not a function
定位过程
- 首先目的是小版本升级一下 handsontable,并在此过程中解决样式问题,原先版本为 0.31.2, 4 年前发布,考虑升级至最小且有官方文档的 4.0.0 版本
- 升级前没有
promise.finally is not a function
问题 - 查看项目代码,发现有配置
babel-polyfill
且在main.js
有实现promise.finally()
方法的polyfill
- 考虑可能是 polyfill 方法功能不足,使用
promise.prototype.finally
库代替,并正确引入项目,在出现is not a function
的页面引入(require('promise.prototype.finally').shim()
) - 此时项目运行正常,但不久后出现新的问题:其他部分页面在刷新后仍有相同报错,依次引入可解决,但不可取
- 与此同时,提交后发布到测试环境时,出现问题,项目启动后报错
cannot find module es-abstract...
,无法打开项目,但本地环境正常 - 经排查发现
webpack
和promise.prototype.finally
都有引用到 es-abstract,猜测版本不兼容,降低版本后,项目正常启动 - 令外,项目的发版已配置环境自动安装依赖,且环境与管理工具为 node8 和 yarn,而本地环境为 node10,所以本地环境正常,测试环境报错
- 同步环境后,问题尚未解决,消失已久的
promise.finally is not a function
再次出现 - 总结可能导致出现问题的点
- axios:
项目使用 axios 处理网络请求,并增加拦截器;发现手动 new 的 promise 实例上有 polyfill 后的 finally 方法,而this.$http.get
返回的 promise 对象上没有 - 新版本的 handsontable 增加了与 promise 相关的处理
- 首先排除一个正确答案,先把 axios 解决掉。经过多次尝试,axios 返回的 promise 对象在其他页面正常使用 finally 方法,而在使用 handsontable 的页面返回的 promise 对象与它们不同,是经过复写的,那么问题的关键就在于 handsontable 对 promise 改写了什么
1
- 源码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24Internal = 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 方法
那么怎么解决这个问题呢,每个页面都通过 shim 覆盖一次,显然不够优雅,关键还是回到 handsontable 本身来:
一是改写源码当中对 promise 的处理,增加 finally 等常用方法;
二是考虑项目当中对它的调用方式,如果在源头把 handsontable 对 promise 的处理纠正回我们需要的那个 promise,那么每次创建 handsontable 实例的时候就会自动纠正,而不需要我们在每个页面手动进行
由于到此为止使用的 handsontable 版本还是 4.0.0,也就是 4 年前的,我想最新的版本肯定会增加对 已加入 promise 标准的这几个方法的处理,果然与我所想相差无几,在最新的 12.1.2 版本上,
Promise.all
、race
、resolve
、reject
、catch
都有了,但唯独没有finally
方法,猜对了,但没完全对没办法,那就回到第二步,在调用 handsontable 的源头统一处理。通过一个自定义的组件来创建 handsontable 实例,并传入配置项,这个组件就是最好的解决问题的地方,和预期相同,通过覆盖 handsontable 的 Promise,解决了问题,不过没有使用
promise.prototype.finally
这个库,而是发现项目当中有使用到一个较为老版本的bluebird.core.js
,同样是基于 promise 的库,但更加注重新功能和性能,在老项目当中使用还是很好的体验