聊一聊 JavaScript 中的錯誤隔離
接口請求失敗、接口中部分數據缺失、運營數據不符合預期… 當我們的應用發布上線后,就開始面臨這些風險。
而一旦這些問題導致了 JavaScript 報錯(如空指針異常),并且沒有被有效地隔離,就有可能引發頁面的白屏、無法交互等線上問題。
在雙 11 準備期間,我們收集了過往一年前端相關的線上問題,在收集的 21 個案例中,竟有一半的問題都與「數據異常觸發頁面顯示異常」這個原因有些相關。
如何將錯誤的影響隔離在一定范圍內,顯得尤為重要。
這篇文章就和大家一起來聊一聊我們嘗試過的一些方案,及遇到的問題。
從空指針異常說起
數據引發的最常見的問題就是空指針異常。
var result = a.b.c.d;
這樣的代碼如同地雷,一旦 a 是一個動態數據,那么問題一觸即發。
封裝一個 get 的方法來取值,當數據不存在時,返回 undefined ,可以快速避免此類問題。
var result = get(a, 'b.c.d');
但如同我們期望大家在取值前,都先做判斷一樣,并不能保證所有人都這么用了,用不用全靠自覺。
if (a && a.b && a.b.c) {
var result = a.b.c.d;
}
所以,有了以下的一些方案:
異步數據校驗
對異步數據校驗的想法是,在數據獲取后、使用前,先做一遍schema校驗,檢測重要數據缺失、類型不對等異常情況。
與此方案對應的,我們在 fetch 的基礎上封裝了 fetch-checker 注1 組件。
fetch-checker 強制要求用戶在請求數據的同時,提供數據對應的 schema:
let schema = {
"rule": {
"type": "string",
},
"banner": {
"type": "object",
"required": true,
"default": {
"url": "https://item.taobao.com/item.htm?id=527331762117"
}
}
};
這份 schema 需要描述:
- 每個字段的類型
- 字段是否 required
- 當 required 的字段缺失時,是否需要打底數據
fetch-checker 在拿到數據后,先做一層校驗,如有需要的話,補上缺失的數據,然后再返回給調用者。這樣,使用者拿到的數據就一定是符合預期的。
然而,這個方案面臨的挑戰是:
- 如何確保調用者提供了完整的 schema 描述。不想寫 schema,完全可以提供一個粗略的 schema 描述,來通過校驗。
- schema 如何精簡。即不會對 bundle 大小造成太大影響,又能滿足校驗的功能。
代碼編譯
受 babel 的啟發,這個方案是對存在 NPE 隱患的代碼,在編譯階段,將其轉換成等價的安全代碼。如下所示:
var a = {};
// input
var result = a.b.c;
// output
var result = (_object2 = (_object3 = a) == null ? null : _object3.b) == null ? null : _object2.c;
當 a 為空對象時,執行編譯后的代碼會返回 null ,從而避免因為代碼拋錯,阻斷后續進程。
在 babel-plugin-safe-member-expression 注2 這個 Babel 插件中,我們做了上述的嘗試。目前,cake項目中,已經可以通過 enableSafeMemberExpression 這個配置,選擇性的啟用該功能。
這個方案相比來說接入成本較低,開發者無需對現有的代碼做出調整,但同樣存在挑戰:
- 開發階段問題不易暴露,明明應該報錯的場景,卻沒有任何反饋。理想的狀態是:開發調試階段盡可能多的暴露問題,線上則盡可能的減少報錯。
- 隱患的代碼如何界定。目前所有的 a.b 的調用方式都會按上述方案進行編譯,雖然測試過程中還沒有發現問題,但只處理有隱患的代碼才更安全。
靜態校驗
以 flow 為代表的靜態校驗工具,可以在一定程度上檢測出 NPE 隱患。
type res = {
data ?: Object
}
let name = res.data.name;
// property `name`. Propery cannot be accessed on possibly undefined value
如上面的代碼所描述的,使用者需要首先理清自己的數據是否允許為空值,當 data 被允許為空值時,通過 flow 檢測, data.name 類似這樣調用便會被檢測出錯誤。
然而,如何來推進所有的業務都接入靜態校驗,接入后,又如何保證開發者描述了所有的類型,卻同樣是個難點。
小結
總結以上幾種方案,各有優缺點,都還不能算做最理想的解決方案。
方案名稱 | 優勢 | 缺點 |
---|---|---|
提前判斷 | 實行簡單 | 全靠自覺 |
異步數據校驗 | 可確保所使用的數據是滿足預期的 | schema 描述成本高 |
代碼編譯 | 接入成本低,易執行 | 開發階段不易暴露問題 |
靜態校驗 | 對現有代碼邏輯侵入少 | 落地成本高 |
對于業務來說,最愿意使用和有效的方案一定是:
- 能將線上問題隔離在一個小范圍內,同時不影響開發調試階段的問題暴露
- 能提前暴露出隱患
- 接入成本低,不需要大量修改現有業務代碼
關于空指針異常和錯誤隔離,機智的你又有哪些方案,一起來討論吧。
來自:http://taobaofed.org/blog/2016/11/10/prevent-prop-access-error-in-js/