沒想到會是在雙十一這麼忙的時間段把這篇文章寫完 😂,公司很忙很緊張,可我還有時間在公司作分享,寫博文,慚愧慚愧... 作後臺系統在雙十一期間不如 2c 端的小夥伴有參與感呀。vue
上文 接口異常狀態統一處理方案:優先業務端處理,再按需統一處理。 最後提出可能存在的問題:node
若是項目是由 vue-cli 搭建的 webpack 模板項目,在沒有修改 .babelrc 文件配置的狀況下,此方案在 Firefox 瀏覽器下是無效的。接口狀態異常的狀況下,老是會執行統一處理,不會先交由業務端處理異常,再斷定是否執行統一處理。webpack
經過 debug 發現,handleAPIStatusError
函數老是在 catch
函數以前先執行,致使每次都能在 handleAPIStatusError
函數內找到未處理的異常接口的 apiUid
。這就奇怪了,Promise
是 micro-task,setTimeout
是 macro-task,總不多是 EvenLoop 的問題吧 🙄,不可能不可能,想多了。💀
那究竟是什麼緣由呢?只能走代碼了,Go Go Go... 而後在 Firefox 開發者工具內,發如今 polyfill.js
內 Promise
的墊片函數 then
和 catch
內部,this
的屬性中竟然沒有 apiUid
!並且還有一堆莫名其妙不明因此的屬性。😳
怎麼肥四?🤔ios
我表示看不懂,this
不該該是 Promise
的實例嗎?如今這個是個什麼鬼?💀git
通過一番掙扎,忽然發現:
「咦,Promise
哪去了?」
「那個 __WEBPACK_IMPORTED_MODULE_0_babel_runtime_core_js_promise___default
是什麼鬼?」
而後... 忽然醒悟 😠es6
babel_runtime_core_js_promise
?
core_js
? babel-polyfill
?
babel_runtime
? babel-transform-runtime
?
難道 Firefox 內置的 Promise
是假的?
一臉驚悚... 😱github
帶着疑惑的心態去查找 babel-polyfill
的源碼,找到 Promise
的墊片函數:web
*** node_modules/core-js/modules/es6.promise.js ***
// ...
var USE_NATIVE = !!(function() {
try {
// correct subclassing with @@species support
var promise = $Promise.resolve(1);
var FakePromise = ((promise.constructor = {})[
require("./_wks")("species")
] = function(exec) {
exec(empty, empty);
});
// unhandled rejections tracking support, NodeJS Promise without it fails @@species test
return (
(isNode || typeof PromiseRejectionEvent == "function") &&
promise.then(empty) instanceof FakePromise
);
} catch (e) {
/* empty */
}
})();
// ...
複製代碼
這個 USE_NATIVE
就是斷定是否使用瀏覽器內置 Promise
的關鍵變量,果不其然,在 Firefox 中,這個值爲 false
;再去 Chrome 查看,發現值爲 true
,我想,問題的根源已經找到了。vue-cli
爲何 Firefox 中 USE_NATIVE
的值爲 false
,經過跟蹤代碼發現,關鍵點在於 PromiseRejectionEvent
這個接口,Firefox 中並無實現。axios
梳理一下,因爲 Firefox 沒有實現 PromiseRejectionEvent
接口,致使 babel-polyfill
在斷定是否使用 Promise
墊片函數時,認爲當前運行環境是須要使用的,因此 Firefox 下的 Promise 被覆寫。而後由於 babel-transform-runtime
插件的關係,爲了不全局污染,又將 Promise
作了模塊化處理,也就是業務代碼中的 Promise
全都使用的是被 babel-transform-runtime
模塊化轉換後的 Promise
。
雖然說是作了模塊化處理 Promise
,那爲何接口層 Promise
實例的屬性中會沒有 apiUid
?
表象是缺失了 apiUid
屬性,那就從接口請求的根源開始找緣由,從最開始請求的調用函數開始調試。
就當進入到 axios 源碼調試時發現:
axios 源碼裏面使用的 Promise
是全局的,也就是說咱們業務代碼內的 Promise
與 axios 使用的 Promise
不是同一個 Promise
,呃... 😳
爲何會這樣?仍是得從根源思考,webpack 打包構建咱們的業務代碼,.js
文件的處理都是經過 babel-loader
插件,wait...wait...wait... axios 是屬於第三方依賴,文件位置處於 node_modules
目錄下,babel-loader
確定是作了 include
或 exclude
配置的,也就是說 axios 的源碼並無被 babel-transform-runtime
作處理,嗦嘎... 😤
*** webpack.base.conf.js ***
// ...
{
test: /\.js$/,
loader: 'babel-loader?cacheDirectory',
include: [
resolve('src'),
resolve('test'),
resolve('node_modules/webpack-dev-server/client'),
],
},
// ...
複製代碼
OK,爲何業務代碼與 axios 源碼使用的不是同一個 Promise
的緣由已經找到了,那對咱們的接口異常處理邏輯又有什麼影響了?
因爲 axios 源碼內的 Promise
沒有被 babel-transform-runtime
模塊化處理,接口調用底層使用的也就是全局的Promise
,也就是說被覆寫的 axios.Axios.prototype.request
函數返回的是全局 Promise
的實例。
那其實接口調用時使用的 then
方法和 catch
方法都是全局 Promise
的實例方法,與咱們在 polyfill.js
內覆寫 Promise.prototype.then
方法和 Promise.prototype.catch
方法毫無瓜葛。理所固然,咱們的異常狀態統一處理方案確定就沒法生效。
問題的緣由已經找到,那思考如何解決吧。
既然是 babel-polyfill
代碼形成的問題,那移除 babel-polyfill
?
確定不行,ES6+ 的實例方法怎麼辦?
那既然是 babel-transform-runtime
插件將 Promise
作了模塊化處理,那移除 babel-transform-runtime
?
也不行,這樣 babel 就會在每一個打包後的文件內插入重複相同的 helper 函數。
這樣不行,那也不行,到底咋整?🤔
仍是得從問題根源出發,咱們須要 babel-transform-runtime
模塊化 helper 代碼,也須要 babel-polyfill
提供實例方法兼容瀏覽器。這其中就有一個挺矛盾的問題,babel-transform-runtime
會模塊化代碼,而 babel-polyfill
又污染全局環境,應該怎麼解開這其中的糾葛?
其實這時候就須要咱們弄清 babel-transform-runtime
和 babel-polyfill
它們的使用場景,它們是爲了解決什麼問題而產生的。
babel-transform-runtime
的產生主要是爲了解決 library 代碼須要轉換成 ES5,但又不肯定宿主環境,沒法直接使用 babel-polyfill
;代碼轉化過程當中會產生一些 helper 函數,在多文件的狀況下就會在多個文件內都添加 helper 函數,致使沒必要要的重複,因此進行模塊化 helper 函數處理;爲了避免污染全局環境,會將 polyfill 和 regenerator 函數也進行模塊化處理。
因此 babel-transform-runtime
主要解決 library 不肯定宿主和 helper 代碼模塊化等相關的問題。
babel-polyfill
提供的就是完整的墊片函數(API、靜態方法、實例方法),以兼容目前各家瀏覽器規範不統一的問題。
以上,就是 babel-transform-runtime
和 babel-polyfill
應用場景和產生背景。那針對咱們系統而言,確定是必須使用 babel-polyfill
的,由於 babel 沒法轉譯實例方法,因此咱們須要拿 babel-transform-runtime
開刀。
稍做思考,屢屢其中的關係就會發現,針對咱們系統應用程序而言,須要 babel-transform-runtime
對 polyfill 進行模塊化嗎?並且咱們還必須使用 babel-polyfill
。
因此,解決方案就很清晰了,將 babel-transform-runtime
的 polyfill
配置設置爲 false
便可。
*** .babelrc ***
// ...
"plugins": [
"transform-vue-jsx",
["transform-runtime", {
"polyfill": false
}]
]
// ...
複製代碼
貌似 regenerator
函數也不必模塊化,不過咱們暫時無論它吧。
Over