本自述文件包含我多年來學到的有關處理 JavaScript 錯誤、將錯誤報告給伺服器以及解決許多可能使這一切變得非常困難的錯誤的資訊。瀏覽器在這方面已經有所改進,但仍有改進的空間,以確保所有應用程式都能理智、健全地處理發生的任何錯誤。
本指南中內容的測試案例可以在 https://mknichel.github.io/javascript-errors/ 中找到。
目錄
介紹
JavaScript 錯誤剖析
產生 JavaScript 錯誤
錯誤訊息
堆疊追蹤格式
捕獲 JavaScript 錯誤
視窗錯誤
嘗試/捕捉
受保護的入口點
承諾
網路工作者
Chrome 擴充功能
捕獲、報告和修復錯誤是任何應用程式的重要組成部分,以確保應用程式的健康和穩定性。由於 JavaScript 程式碼也在客戶端和許多不同的瀏覽器環境中執行,因此掌握應用程式中的 JS 錯誤也可能很困難。沒有關於如何報告 JS 錯誤的正式 Web 規範,這會導致每個瀏覽器的實作有差異。此外,瀏覽器的 JavaScript 錯誤實作中也存在許多錯誤,這使得這變得更加困難。本頁面將介紹 JS 錯誤的這些方面,以便未來的開發人員能夠更好地處理錯誤,瀏覽器有望收斂於標準化解決方案。
JavaScript 錯誤由兩個主要部分組成:錯誤訊息和堆疊追蹤。錯誤訊息是一個字串,描述出了什麼問題,堆疊追蹤描述了程式碼中發生錯誤的位置。 JS 錯誤可以由瀏覽器本身產生,也可以由應用程式程式碼引發。
當一段程式碼沒有正確執行時,瀏覽器可能會拋出JS錯誤,也可能由程式碼直接拋出。
例如:
var a = 3;a();
在此範例中,實際上是數字的變數不能作為函數呼叫。瀏覽器會拋出類似TypeError: a is not a function
with a stack trace that point of that line of code 的錯誤。
如果不滿足某個先決條件,開發人員可能還想在一段程式碼中拋出錯誤。例如
if (!checkPrecondition()) { throw new Error("不符合前提條件!");}
在這種情況下,錯誤將是Error: Doesn't meet precondition!
。此錯誤還將包含指向對應行的堆疊追蹤。瀏覽器和應用程式程式碼拋出的錯誤可以進行相同的處理。
開發人員可以透過多種方式在 JavaScript 中拋出錯誤:
throw new Error('Problem description.')
throw Error('Problem description.')
<-- 相當於第一個
throw 'Problem description.'
<-- 不好
throw null
<-- 更糟
實際上不建議拋出字串或 null,因為瀏覽器不會將堆疊追蹤附加到該錯誤,從而丟失程式碼中發生該錯誤的上下文。最好拋出一個實際的 Error 對象,其中包含錯誤訊息以及指向發生錯誤的正確程式碼行的堆疊追蹤。
每個瀏覽器都有自己的一組訊息,用於內建異常,例如上面嘗試呼叫非函數的範例。瀏覽器將嘗試使用相同的訊息,但由於沒有規範,因此無法保證這一點。例如,Chrome 和 Firefox 都使用{0} is not a function
,而 IE11 將報告Function expected
(特別是也不報告嘗試呼叫什麼變數)。
然而,瀏覽器也經常出現分歧。當switch
語句中存在多個 default 語句時,Chrome 會拋出"More than one default clause in switch statement"
而 Firefox 則會報告"more than one switch default"
。隨著新功能添加到網路中,這些錯誤訊息必須更新。當您嘗試處理混淆程式碼報告的錯誤時,這些差異可能會在稍後發揮作用。
您可以在以下位置找到瀏覽器用於錯誤訊息的範本:
火狐 - http://mxr.mozilla.org/mozilla1.9.1/source/js/src/js.msg
Chrome - https://code.google.com/p/v8/source/browse/branches/bleeding_edge/src/messages.js
Internet Explorer - https://github.com/Microsoft/ChakraCore/blob/4e4d4f00f11b2ded23d1885e85fc26fcc96555da/lib/Parser/rterrors.h
對於某些異常,瀏覽器會產生不同的錯誤訊息。
堆疊追蹤是對程式碼中發生錯誤的位置的描述。它由一系列幀組成,其中每個幀描述程式碼中的特定行。最上面的幀是引發錯誤的位置,而後續幀是函數調用堆疊 - 或如何執行程式碼以到達引發錯誤的位置。由於 JavaScript 通常是連接和縮小的,因此列號也很重要,以便在給定行有多個語句時可以找到準確的語句。
Chrome 中的基本堆疊追蹤如下所示:
at throwError (http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:9) at http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3
每個堆疊幀由一個函數名稱(如果適用且程式碼不在全域範圍內執行)、它來自的腳本以及程式碼的行號和列號組成。
不幸的是,堆疊追蹤格式沒有標準,因此這因瀏覽器而異。
Microsoft Edge 和 IE 11 的堆疊追蹤看起來與 Chrome 類似,只是它明確列出了全域程式碼:
at throwError (http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:3) at Global code (http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3)
Firefox 的堆疊追蹤如下所示:
throwError@http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:9 @http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3
Safari 的格式與 Firefox 的格式類似,但也略有不同:
throwError@http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:18 global code@http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:13
基本資訊相同,但格式不同。
另請注意,在 Safari 範例中,除了格式與 Chrome 不同之外,列號也與 Chrome 和 Firefox 不同。在不同的錯誤情況下,列號也可能偏差較大 - 例如在程式碼中(function namedFunction() { throwError(); })();
,Chrome 將報告throwError()
函數呼叫的列,而 IE11 則將列號報告為字串的開頭。當伺服器需要解析堆疊追蹤以查找報告的錯誤並對混淆的堆疊追蹤進行反混淆時,這些差異將在稍後重新發揮作用。
有關錯誤堆疊屬性的更多信息,請參閱 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack。存取 Error.stack 屬性時,Chrome 確實將錯誤訊息包含在堆疊中,但 Safari 10+ 則不會。
堆疊追蹤的格式因瀏覽器使用的形式和列號而異。
進一步深入了解,堆疊追蹤格式有很多細微差別,將在下面幾節中討論。
預設情況下,匿名函數沒有名稱,並且在堆疊追蹤的函數名稱中顯示為空字串或「匿名函數」(取決於瀏覽器)。為了改進偵錯,您應該為所有函數新增名稱,以確保它出現在堆疊幀中。最簡單的方法是確保使用名稱指定匿名函數,即使名稱未在其他任何地方使用。例如:
setTimeout(函數名稱OfTheAnonymousFunction() { ... }, 0);
這將導致堆疊追蹤來自:
at http://mknichel.github.io/javascript-errors/javascript-errors.js:125:17
到
at nameOfTheAnonymousFunction (http://mknichel.github.io/javascript-errors/javascript-errors.js:121:31)
在 Safari 中,這將來自:
https://mknichel.github.io/javascript-errors/javascript-errors.js:175:27
到
nameOfTheAnonymousFunction@https://mknichel.github.io/javascript-errors/javascript-errors.js:171:41
此方法可確保nameOfTheAnonymousFunction
出現在該函數內部任何程式碼的框架中,讓偵錯更加容易。請參閱 http://www.html5rocks.com/en/tutorials/developertools/async-call-stack/#toc-debugging-tips 以了解更多資訊。
如果函數本身沒有名稱,瀏覽器也會使用函數指派給的變數或屬性的名稱。例如,在
var fnVariableName = 函數() { ... };
瀏覽器將使用fnVariableName
作為堆疊追蹤中函數的名稱。
at throwError (http://mknichel.github.io/javascript-errors/javascript-errors.js:27:9) at fnVariableName (http://mknichel.github.io/javascript-errors/javascript-errors.js:169:37)
比這更微妙的是,如果這個變數是在另一個函數中定義的,那麼所有瀏覽器都將僅使用該變數的名稱作為堆疊追蹤中的函數名稱,但Firefox 除外,它將使用不同的形式來連接變數的名稱外部函數與內部變數的名稱。例子:
函數 throwErrorFromInnerFunctionAssignedToVariable() { var fnVariableName = function() { throw new Error("foo"); } }; fn變數名();}
將在 Firefox 中產生:
throwErrorFromInnerFunctionAssignedToVariable/fnVariableName@http://mknichel.github.io/javascript-errors/javascript-errors.js:169:37
在其他瀏覽器中,這看起來像是:
at fnVariableName (http://mknichel.github.io/javascript-errors/javascript-errors.js:169:37)
Firefox 對另一個函數中定義的函數使用不同的堆疊框架文字。
在 IE11 之外的所有主要瀏覽器中,函數的顯示名稱也可以透過displayName
屬性來設定。在這些瀏覽器中,displayName 將出現在 devtools 偵錯器中,但在 Safari 以外的所有瀏覽器中,它不會在錯誤堆疊追蹤中使用(Safari 與其他瀏覽器的不同之處在於,它還在與錯誤關聯的堆疊追蹤中使用displayName)。
var someFunction = function() {};someFunction.displayName = " # 函數的詳細描述。";
displayName 屬性沒有官方規範,但所有主要瀏覽器都支援它。請參閱https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/displayName 和http://www.alertdebugging.com/2009/04/29/building-a- better -javascript-profiler-with-webkit/ 以了解有關 displayName 的更多資訊。
IE11 不支援 displayName 屬性。
Safari 使用 displayName 屬性作為錯誤堆疊追蹤中的符號名稱。
如果報告錯誤時沒有堆疊追蹤(請參閱下面發生這種情況的更多詳細資訊),則可以以程式設計方式擷取堆疊追蹤。
在 Chrome 中,透過使用Error.captureStackTrace
API 可以輕鬆做到這一點。有關使用此 API 的更多信息,請參閱 https://github.com/v8/v8/wiki/Stack%20Trace%20API。
例如:
函數ignoreThisFunctionInStackTrace() { var err = 新錯誤(); Error.captureStackTrace(err,ignoreThisFunctionInStackTrace); 返回 err.stack;}
在其他瀏覽器中,還可以透過建立新錯誤並存取該物件的堆疊屬性來收集堆疊追蹤:
var err = new Error('');回傳 err.stack;
但是,IE10 僅在實際引發錯誤時填充堆疊追蹤:
嘗試 { 拋出新的錯誤('');} catch (e) { 返回 e.stack;}
如果這些方法都不起作用,那麼可以透過迭代arguments.callee.caller
物件來建立沒有行號或列的粗略堆疊追蹤——但這在ES5嚴格模式下不起作用,並且不是建議的方法。
將非同步點插入到 JavaScript 程式碼中是很常見的,例如當程式碼使用setTimeout
或透過使用 Promises 時。這些非同步入口點可能會導致堆疊追蹤出現問題,因為它們會導致形成新的執行上下文,並且堆疊追蹤再次從頭開始。
Chrome DevTools 支援非同步堆疊追蹤,或者換句話說,確保錯誤的堆疊追蹤也顯示引入非同步點之前發生的幀。透過使用 setTimeout,這將捕獲誰呼叫了最終產生錯誤的 setTimeout 函數。請參閱 http://www.html5rocks.com/en/tutorials/developertools/async-call-stack/ 以了解更多資訊。
非同步堆疊追蹤將如下所示:
throwError @ throw-error.js:2 setTimeout (async) throwErrorAsync @ throw-error.js:10 (anonymous function) @ throw-error-basic.html:14
目前,非同步堆疊追蹤僅在 Chrome DevTools 中受支持,僅適用於 DevTools 開啟時引發的異常。從程式碼中的 Error 物件存取的堆疊追蹤不會將非同步堆疊追蹤作為其一部分。
在某些情況下,可以填充非同步堆疊追蹤,但這可能會對應用程式的效能造成重大影響,因為擷取堆疊追蹤並不便宜。
只有 Chrome DevTools 本身支援非同步堆疊追蹤。
已評估或內聯到 HTML 頁面中的程式碼的堆疊追蹤將使用頁面的 URL 和執行程式碼的行/列號。
例如:
at throwError (http://mknichel.github.io/javascript-errors/throw-error-basic.html:8:9) at http://mknichel.github.io/javascript-errors/throw-error-basic.html:12:3
如果這些腳本實際上來自出於最佳化原因而內聯的腳本,則 URL、行號和列號將是錯誤的。為了解決此問題,Chrome 和 Firefox 支援//# sourceURL=
註解(Safari、Edge 和 IE 不支援)。此註解中指定的 URL 將用作所有堆疊追蹤的 URL,並且將相對於標記而不是 HTML 文件的開頭來計算行號和列號。對於與上面相同的錯誤,使用值為「inline.js」的 sourceURL 註解將產生如下所示的堆疊追蹤:
at throwError (http://mknichel.github.io/javascript-errors/inline.js:8:9) at http://mknichel.github.io/javascript-errors/inline.js:12:3
這是一種非常方便的技術,可以確保即使在使用內聯腳本和 eval 時堆疊追蹤仍然正確。
http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl 更詳細地描述了 sourceURL 註解。
Safari、Edge 和 IE 不支援用於命名內聯腳本和 eval 的 sourceURL 註解。如果您在 IE 或 Safari 中使用內聯腳本並且混淆了程式碼,則將無法反混淆來自這些腳本的錯誤。
在 Chrome 42 之前,Chrome 無法正確計算使用 sourceURL 註解的內嵌腳本的行號。有關更多信息,請參閱 https://bugs.chromium.org/p/v8/issues/detail?id=3920。
使用 sourceURL 註解時,內嵌腳本中的堆疊框架的行號不正確,因為它們相對於 HTML 文件的開頭而不是內聯腳本標記的開頭(使得無法進行正確的反混淆)。 https://code.google.com/p/chromium/issues/detail?id=578269
對於使用 eval 的程式碼,除了是否使用 sourceURL 註解之外,堆疊追蹤中還存在其他差異。在 Chrome 中,eval 中使用的語句的堆疊追蹤可能如下所示:
Error: Error from eval at evaledFunction (eval at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3),:1:36) at eval (eval at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3), :1:68) at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3)
在 MS Edge 和 IE11 中,這看起來像:
Error from eval at evaledFunction (eval code:1:30) at eval code (eval code:1:2) at evalError (http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3)
在 Safari 中:
Error from eval evaledFunction eval code eval@[native code] evalError@http://mknichel.github.io/javascript-errors/javascript-errors.js:137:7
在火狐瀏覽器中:
Error from eval evaledFunction@http://mknichel.github.io/javascript-errors/javascript-errors.js line 137 > eval:1:36 @http://mknichel.github.io/javascript-errors/javascript-errors.js line 137 > eval:1:11 evalError@http://mknichel.github.io/javascript-errors/javascript-errors.js:137:3
這些差異可能導致很難在所有瀏覽器中以相同的方式解析 eval 程式碼。
每個瀏覽器對 eval 內發生的錯誤使用不同的堆疊追蹤格式。
您的 JavaScript 程式碼也可以直接從本機程式碼呼叫。 Array.prototype.forEach
是一個很好的範例 - 你將一個函數傳遞給forEach
,JS 引擎將為你呼叫該函數。
函數 throwErrorWithNativeFrame() { var arr = [0, 1, 2, 3]; arr.forEach(函數namedFn(value) { throwError(); });}
這會在不同的瀏覽器中產生不同的堆疊追蹤。 Chrome 和 Safari 將本機函數的名稱作為單獨的框架附加在堆疊追蹤本身中,例如:
(Chrome) at namedFn (http://mknichel.github.io/javascript-errors/javascript-errors.js:153:5) at Array.forEach (native) at throwErrorWithNativeFrame (http://mknichel.github.io/javascript-errors/javascript-errors.js:152:7) (Safari) namedFn@http://mknichel.github.io/javascript-errors/javascript-errors.js:153:15 forEach@[native code] throwErrorWithNativeFrame@http://mknichel.github.io/javascript-errors/javascript-errors.js:152:14 (Edge) at namedFn (http://mknichel.github.io/javascript-errors/javascript-errors.js:153:5) at Array.prototype.forEach (native code) at throwErrorWithNativeFrame (http://mknichel.github.io/javascript-errors/javascript-errors.js:152:7)
但是,Firefox 和 IE11並未顯示forEach
作為堆疊的一部分被呼叫:
namedFn@http://mknichel.github.io/javascript-errors/javascript-errors.js:153:5 throwErrorWithNativeFrame@http://mknichel.github.io/javascript-errors/javascript-errors.js:152:3
某些瀏覽器在堆疊追蹤中包含本機程式碼幀,而其他瀏覽器則不包含。
要檢測您的應用程式有錯誤,某些程式碼必須能夠捕獲該錯誤並報告該錯誤。有多種捕獲錯誤的技術,每種技術都有其優點和缺點。
window.onerror
是開始捕捉錯誤的最簡單、最好的方法之一。透過將window.onerror
分配給函數,應用程式的其他部分未捕獲的任何錯誤都會報告給該函數,以及有關該錯誤的一些資訊。例如:
window.onerror = 函數(msg, url, line, col, err) { console.log('應用程式遇到錯誤:' + msg); console.log('堆疊追蹤:' + err.stack);}
https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onerror 更詳細地描述了這一點。
從歷史上看,這種方法存在一些問題:
沒有提供錯誤對象
window.onerror
函數的第五個參數應該是一個 Error 物件。這已於 2013 年新增至 WHATWG 規格:https://html.spec.whatwg.org/multipage/webappapis.html#errorevent。 Chrome、Firefox 和 IE11 現在可以正確提供 Error 物件(以及關鍵堆疊屬性),但 Safari、MS Edge 和 IE10 則不提供。自Firefox 14 (https://bugzilla.mozilla.org/show_bug.cgi?id=355430) 起,此功能適用於Firefox,自2013 年底以來,此功能適用於Chrome (https://mikewest.org/2013 /08/debugging-runtime-errors) -with-window-onerror,https://code.google.com/p/chromium/issues/detail?id=147127)。 Safari 10 在 window.onerror 中推出了對 Error 物件的支援。
Safari(版本低於 10)、MS Edge 和 IE10 不支援在 window.onerror 中包含堆疊追蹤的 Error 物件。
跨域清理
在 Chrome 中,來自 window.onerror 處理程序中另一個網域的錯誤將被清理為「腳本錯誤。」、「」、0。您不關心的腳本,因此應用程式可以過濾掉如下所示的錯誤。然而,這種情況在 Firefox、Safari 或 IE11 中不會發生,Chrome 也不會為包裝違規程式碼的 try/catch 區塊執行此操作。
如果您希望從跨網域腳本中完全保真地接收 Chrome 中window.onerror
中的錯誤,這些資源必須提供適當的跨來源標頭。有關更多信息,請參閱 https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror。
Chrome 是唯一能夠清除其他來源的錯誤的瀏覽器。請注意過濾掉這些內容,或設定適當的標頭。
Chrome 擴充功能
在舊版的 Chrome 中,安裝在使用者電腦上的 Chrome 擴充功能也可能會引發錯誤,並回報給 window.onerror。此問題已在較新版本的 Chrome 中修復。請參閱下面專門的 Chrome 擴充部分。
window.addEventListener("error")
API 的運作方式與 window.onerror API 相同。有關此方法的更多信息,請參閱 http://www.w3.org/html/wg/drafts/html/master/webappapis.html#runtime-script-errors。
透過 window.onerror 擷取錯誤並不能阻止該錯誤也出現在 DevTools 控制台中。這很可能是正確的開發行為,因為開發人員可以輕鬆地看到錯誤。如果您不希望這些錯誤在生產中向最終用戶顯示,則可以在使用 window.addEventListener 方法時呼叫e.preventDefault()
。
window.onerror是捕捉和報告JS錯誤的最佳工具。建議僅將具有有效 Error 物件和堆疊追蹤的 JS 錯誤報告回伺服器,否則錯誤可能難以調查,或者您可能會從 Chrome 擴充功能或跨網域腳本收到大量垃圾郵件。
鑑於上述部分,不幸的是不可能在所有瀏覽器中依賴window.onerror
來捕獲所有錯誤訊息。對於本機擷取異常,try/catch 區塊是顯而易見的選擇。整個 JavaScript 檔案也可以包裝在 try/catch 區塊中,以擷取 window.onerror 無法擷取的錯誤訊息。這改善了不支援 window.onerror 的瀏覽器的情況,但也有一些缺點。
try/catch 區塊不會捕捉程式中的所有錯誤,例如透過window.setTimeout
從非同步程式碼區塊引發的錯誤。 Try/catch 可以與受保護的入口點一起使用來幫助填補空白。
包裝整個應用程式的 try/catch 區塊不足以捕獲所有錯誤。
舊版的 V8(以及可能的其他 JS 引擎),包含 try/catch 區塊的函式不會被編譯器最佳化 (http://www.html5rocks.com/en/tutorials/speed/v8/)。 Chrome 在 TurboFan 中修正了此問題 (https://codereview.chromium.org/1996373002)。
JavaScript 的「入口點」是任何可以開始執行程式碼的瀏覽器 API。範例包括setTimeout
、 setInterval
、事件偵聽器、XHR、Web 套接字或 Promise。從這些入口點拋出的錯誤將被 window.onerror 捕獲,但是在不支援 window.onerror 中完整 Error 物件的瀏覽器中,需要一種替代機制來捕獲這些錯誤,因為提到了 try/catch 方法上面也不會抓住他們。
值得慶幸的是,JavaScript 允許包裝這些入口點,以便在呼叫函數之前插入 try/catch 區塊以捕獲程式碼引發的任何錯誤。
每個入口點需要稍微不同的程式碼來保護入口點,但該方法的要點是:
函數保護入口點(fn){ return function protectedFn() {try { return fn();} catch (e) { // 處理錯誤。 }}_oldSetTimeout = window.setTimeout;window.setTimeout = 函數 protectedSetTimeout(fn, time) { 返回_oldSetTimeout.call(窗口,protectEntryPoint(fn),時間);};
遺憾的是,Promise 中發生的錯誤很容易被忽略和報告。 Promise 中發生但未透過附加拒絕處理程序處理的錯誤不會在其他任何地方報告 - 它們不會報告給window.onerror
。即使 Promise 附加了拒絕處理程序,但程式碼本身也必須手動報告這些錯誤才能記錄它們。請參閱 http://www.html5rocks.com/en/tutorials/es6/promises/#toc-error-handling 以了解更多資訊。例如:
window.onerror = 函數(...) { // 這永遠不會被 Promise 程式碼呼叫。 throw new Error("此錯誤將不會在任何地方處理。");});var p2 = new Promise(...);p2.then(function() { throw new Error("這個錯誤將在鏈中處理。");}).catch(function(error) { // 向使用者顯示錯誤訊息 // 此程式碼應手動報告錯誤,以便將其記錄在伺服器上(如果適用)。
捕獲更多資訊的一種方法是使用受保護的入口點透過 try/catch 來包裝 Promise 方法的呼叫以報告錯誤。這可能看起來像:
var _oldPromiseThen = Promise.prototype.then; Promise.prototype.then = function protectedThen(callback, errorHandler) {return _oldPromiseThen.call(this,protectEntryPoint(callback),protectEntryPoint(errorHandler)); };
遺憾的是,Promise 中的錯誤預設不會被處理。
Promise 實作(例如 Q、Bluebird 和 Closure)以不同的方式處理錯誤,這比 Promise 的瀏覽器實作中的錯誤處理更好。
在 Q 中,您可以透過呼叫.done()
來「結束」Promise 鏈,這將確保如果鏈中未處理錯誤,它將被重新拋出並報告。請參閱 https://github.com/kriskowal/q#handling-errors
在 Bluebird 中,未處理的拒絕會立即被記錄並報告。請參閱http://bluebirdjs.com/docs/features.html#surface-unhandled-errors
在 Closure 的 goog.Promise 實作中,如果 Promise 中沒有鏈在可配置的時間間隔內處理拒絕,則會記錄和報告未處理的拒絕(以便允許程式中稍後的程式碼新增拒絕處理程序)。
上面的非同步堆疊追蹤部分討論了當存在非同步掛鉤(例如呼叫Promise.prototype.then
)時,瀏覽器不會捕獲堆疊資訊。 Promise polyfill 提供了一種捕捉非同步堆疊追蹤點的方法,這可以使診斷錯誤變得更加容易。這種方法很昂貴,但它對於捕獲更多調試資訊非常有用。
在Q中,呼叫Q.longStackSupport = true;
。請參閱 https://github.com/kriskowal/q#long-stack-traces
在 Bluebird 中,在應用程式中的某個位置呼叫Promise.longStackTraces()
。請參閱http://bluebirdjs.com/docs/features.html#long-stack-traces。
在 Closure 中,將goog.Promise.LONG_STACK_TRACES
設為 true。
Chrome 49 增加了對 Promise 被拒絕時調度的事件的支援。這允許應用程式掛鉤 Promise 錯誤,以確保它們與其他錯誤一起集中報告。
window.addEventListener('unhandledrejection', event => { // event.reason 包含拒絕原因。當拋出錯誤時,這是 Error 物件。
有關詳細信息,請參閱 https://googlechrome.github.io/samples/promise-rejection-events/ 和 https://www.chromestatus.com/feature/4805872211460096。
任何其他瀏覽器都不支援此功能。
Web Worker,包括專用 Worker、共享 Worker 和服務 Worker,在當今的應用程式中變得越來越流行。由於所有這些工作人員都是與主頁分開的腳本,因此它們每個都需要自己的錯誤處理程式碼。建議每個工作腳本安裝自己的錯誤處理和報告程式碼,以最大程度地有效處理工作人員的錯誤。
專用 Web Worker 在與主頁不同的執行上下文中執行,因此上述機制不會捕獲來自 Worker 的錯誤。需要採取額外的步驟來捕獲頁面上工作人員的錯誤。
建立worker時,可以在新worker上設定onerror屬性:
var worker = new Worker('worker.js');worker.onerror = function(errorEvent) { ... };
這是在 https://html.spec.whatwg.org/multipage/workers.html#handler-abstractworker-onerror 中定義的。 Worker 上的onerror
函數有與上面討論的window.onerror
不同的簽章。 worker.onerror
不接受 5 個參數,而是採用單一參數: ErrorEvent
物件。該物件的 API 可以在 https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent 找到。它包含訊息、檔案名稱、行和列,但目前沒有穩定的瀏覽器包含包含堆疊追蹤的「Error」物件(errorEvent.error 為 null)。由於此 API 是在父頁面的範圍內執行的,因此使用與父頁面相同的報告機制將很有用;不幸的是,由於缺乏堆疊跟踪,該 API 的用途有限。
在工作執行緒運行的 JS 內部,您也可以定義一個遵循常見 window.onerror API 的 onerror API:https://html.spec.whatwg.org/multipage/webappapis.html#onerroreventhandler。在工人代碼中:
self.onerror = 函數(訊息, 檔名, 行, 列, 錯誤) { ... };
該 API 的討論主要遵循上面針對 window.onerror 的討論。不過,有兩點值得注意:
Firefox 和 Safari 不會將「錯誤」物件報告為函數的第五個參數,因此這些瀏覽器不會從工作線程取得堆疊追蹤(Chrome、MS Edge 和 IE11 確實會取得堆疊追蹤)。工作執行緒中onmessage
函數的受保護入口點可用於擷取這些瀏覽器的堆疊追蹤資訊。
由於此程式碼在工作程序中執行,因此程式碼必須選擇如何將錯誤報告回伺服器:它必須使用postMessage
將錯誤傳達回父頁面,或安裝 XHR 錯誤報告機制(下面將詳細討論)工人本身。
在 Firefox、Safari 和 IE11 中(但在 Chrome 中則不然),在呼叫了 Worker 自己的 onerror 和頁面設定的 onerror 事件監聽器之後,父頁面的window.onerror
函數也會被呼叫。但是,此 window.onerror 也不會包含錯誤對象,因此也不會具有堆疊追蹤。這些瀏覽器也必須注意不要多次報告工作人員的錯誤。
Chrome 和 Firefox 支援 SharedWorker API,用於在多個頁面之間共用工作執行緒。由於工作線程是共享的,因此它不會專門附加到一個父頁面;儘管 SharedWorker 大多遵循與專用 Web Worker 相同的訊息,但這會導致錯誤處理方式有些差異。
在 Chrome 中,當 SharedWorker 中出現錯誤時,只會呼叫工作人員程式碼本身內的工作人員自己的錯誤處理(就像他們設定self.onerror
一樣)。父頁面的window.onerror
不會被調用,並且Chrome不支援規範中定義的繼承的可以在父頁面中調用的AbstractWorker.onerror
。
在 Firefox 中,這種行為有所不同。共享worker中的錯誤將導致父頁面的window.onerror被調用,但錯誤物件將為null。此外,Firefox 確實支援AbstractWorker.onerror
屬性,因此父頁面可以將自己的錯誤處理程序附加到工作執行緒。但是,當呼叫此錯誤處理程序時,錯誤物件將為 null,因此不會有堆疊跟踪,因此它的用途有限。
共享工作執行緒的錯誤處理因瀏覽器而異。
Service Workers 是一個全新的規範,目前僅在最新的 Chrome 和 Firefox 版本中可用。這些工作人員遵循與專門的網路工作人員相同的討論。
Service Worker是透過呼叫navigator.serviceWorker.register
函數來安裝的。函數傳回一個 Promise,如果安裝 Service Worker 時發生錯誤(例如在初始化期間拋出錯誤),則該 Promise 將被拒絕。此錯誤將只包含字串訊息,不包含任何其他內容。此外,由於 Promise 不會為window.onerror
處理程序報告錯誤,因此應用程式本身必須向 Promise 添加一個 catch 區塊來捕獲錯誤。
navigator.serviceWorker.register('service-worker-installation-error.js').catch(function(error) { // 字串型別錯誤});
就像其他工作人員一樣,服務工作人員可以在服務工作人員中設定self.onerror
函數來捕獲錯誤。 Service Worker 中的安裝錯誤將報告給 onerror 函數,但不幸的是它們不會包含錯誤物件或堆疊追蹤。
Service Worker API 包含從 AbstractWorker 介面繼承的 onerror 屬性,但 Chrome 不執行任何操作