摘要:現(xiàn)狀最近在寫歡迎的時(shí)候,一直為錯(cuò)誤的棧追蹤而愁。由于送入隊(duì)列的是函數(shù),因此在的參數(shù)可以放心地使用。其次,這些函數(shù)并不是立即在中調(diào)用的,而是由專門的隊(duì)列處理代碼來調(diào)用。
本文的講述都是以 Node.js 環(huán)境為例子,而 Node.js 使用的 JavaScript 引擎是 V8,因此理論上 Chrome 也能適用,其它瀏覽器我就不清楚了。
現(xiàn)狀最近在寫 Rize(歡迎 star) 的時(shí)候,一直為錯(cuò)誤的棧追蹤而愁。為什么呢?這要從 Rize 的架構(gòu)說起。
由于 puppeteer 的絕大多數(shù)操作和 API 是異步的,而寫異步代碼的良好寫法是用 ES2017 的 async/await 語法。
但我們都知道,async/await 實(shí)際上返回的是一個(gè) Promise(即使你沒有顯式地 return 什么,它將是 Promise
所以我使用了一個(gè)隊(duì)列來保存用戶想要進(jìn)行的操作。也就是說,用戶在調(diào)用 Rize 的 API 之后,并不會(huì)(也不可能)立即執(zhí)行這些操作,而是放在隊(duì)列中,等待時(shí)機(jī)適合(例如瀏覽器已經(jīng)啟動(dòng)或者上一個(gè)操作已經(jīng)完成)才執(zhí)行。由于送入隊(duì)列的是函數(shù),因此在 push 的參數(shù)可以放心地使用 async/await。
但是,一旦這些操作中出現(xiàn)錯(cuò)誤,錯(cuò)誤的定位變得十分麻煩。
下面這張圖是直接用 Node.js 運(yùn)行一個(gè)腳本的結(jié)果:
下面這張圖是在 Jest 中執(zhí)行一段代碼的結(jié)果:
原因是,
首先,隊(duì)列中的函數(shù)是 async function,這本來就給 debug 帶來麻煩。
其次,這些函數(shù)并不是立即在 API 中調(diào)用的,而是由專門的隊(duì)列處理代碼來調(diào)用。在錯(cuò)誤發(fā)生時(shí),V8 只能跟蹤到那段隊(duì)列處理代碼那里。
這就為用戶帶來麻煩。錯(cuò)誤發(fā)生了,卻只能看著錯(cuò)誤消息一點(diǎn)一點(diǎn)地去試著定位有問題的地方。
探索為此我去閱讀了 Node.js 的官方文檔,看了 Errors 這一部分,不過似乎沒什么收獲。
后來又找到了 TJ Holowaychuk 大神寫的庫 callsite,看看能不能有用。從文檔上看,這個(gè)庫并不適合我的需求。
但我閱讀了 callsite 的源碼,源碼很短,十行不到。我在源碼發(fā)現(xiàn)了一些信息。
callsite 是利用 V8 的 Stack Trace API 來獲取函數(shù)調(diào)用處的一些信息,如文件名,行號(hào)等等。callsite 是如何獲取這些數(shù)據(jù)的呢?
非常簡(jiǎn)單,就一句:
var err = new Error()
對(duì),僅僅是 new 一個(gè) Error 實(shí)例,而且并不是要拋出這個(gè)錯(cuò)誤。
對(duì)比我們平時(shí)的代碼,通常當(dāng)我們 throw 一個(gè)錯(cuò)誤之后,我們能得到一些錯(cuò)誤棧信息。但實(shí)際上,不需要 throw,僅僅是新建一個(gè) Error 實(shí)例,也能讓 V8 記錄下當(dāng)前的調(diào)用棧信息。
解決既然發(fā)現(xiàn)這個(gè)事實(shí),那我們可以在需要記錄調(diào)用棧的地方 new 一個(gè) Error 實(shí)例。(千萬不要把它拋出,不然你后面的代碼就沒法執(zhí)行了)
此時(shí)當(dāng)前的棧信息已經(jīng)被記錄下來,那么我們?cè)鯓尤ナ褂眠@些信息呢?
如果用戶的代碼執(zhí)行正常,那就沒什么關(guān)系了。關(guān)鍵是在發(fā)生錯(cuò)誤的時(shí)候。這里要提一提的是,我的那段隊(duì)列處理代碼是帶有 try…catch 塊的,大概長(zhǎng)這樣:
try { await fn() } catch (error) { throw error } finally { // do some stuff ... }
你可能好奇什么要把捕捉的異常還要拋出,因?yàn)槲蚁胍氖呛竺娴?finally 塊啊,但同時(shí)我又希望異常能繼續(xù)被拋出。
在這里,我們就要對(duì) catch 塊做點(diǎn)功夫。當(dāng)然這個(gè) try…catch 塊是能夠獲取到之前新建的 Error 實(shí)例的,在這里我省略了那部分代碼。
為了方便敘述,我把之前 new 的那個(gè) Error 實(shí)例命名為 trace,即假設(shè) const trace = new Error()。
顯然把 trace 的所有棧信息都拿過來是不適合的,因?yàn)樗幸恍┪覀儾⒉恍枰臈P畔ⅲㄟ@部分信息是位于 API 調(diào)用處以上的)。
每一個(gè) Error 實(shí)例都有個(gè) stack 屬性,它是一個(gè)多行字符串,我們先把它的每行分開,保存在數(shù)組中:
const stack = trace.stack!.split(" ")
要注意 stack 的第一行不是棧信息,而是錯(cuò)誤消息,這個(gè)不能去掉。所以:
stack.splice(1, 2)
我這里有兩行的信息是沒用的,所以刪去兩行,實(shí)際上要根據(jù)你的需要修改第二個(gè)參數(shù)。
現(xiàn)在可以把 trace 的棧信息替換掉實(shí)際 error 的棧信息:
error.stack = stack.join(" ")結(jié)果
現(xiàn)在就可以得到友好的錯(cuò)誤棧信息了:
配合 Jest 就能更好地定位問題所在之處:
最后是宣傳一下我正在寫的庫 Rize(可以讓你簡(jiǎn)單優(yōu)雅地使用 puppeteer),也就是本文提到的,歡迎前往 GitHub 并 star。
博客原文在這里
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/93303.html
摘要:調(diào)用棧是一種單線程編程語言,這意味著它只有一個(gè)調(diào)用棧。這就是調(diào)用棧的功能。簡(jiǎn)單代碼示例當(dāng)引擎執(zhí)行這段代碼時(shí),調(diào)用棧為空,之后運(yùn)行如下每個(gè)叫做堆棧幀。調(diào)用棧就是通過堆棧幀來追蹤異常,堆棧幀基本就是調(diào)用棧出現(xiàn)異常時(shí)候的狀態(tài)。 概述 幾乎每個(gè)人都已經(jīng)聽說過V8引擎這個(gè)概念,而且大多人都知道JavaScript是單線程的,并且使用回調(diào)隊(duì)列。 這篇文章中,我們將詳細(xì)介紹這些概念,并解釋JavaS...
摘要:在運(yùn)行腳本時(shí),需要顯示的指定對(duì)象。大對(duì)象區(qū)每一個(gè)區(qū)域都是由一組內(nèi)存頁構(gòu)成的。這里是唯一擁有執(zhí)行權(quán)限的內(nèi)存區(qū)。換句話說,是該對(duì)象被之后所能回收到內(nèi)存的總和。一旦活躍對(duì)象已被移出,則在舊的半空間中剩下的任何死亡對(duì)象被丟棄。 內(nèi)存管理 本文以V8為背景 對(duì)之前的文章進(jìn)行重新編輯,內(nèi)容做了很多的調(diào)整,使其具有邏輯更加緊湊,內(nèi)容更加全面。 1. 基礎(chǔ)概念 1.1 生命周期 不管什么程序語言,內(nèi)存...
摘要:是如何工作的引擎,運(yùn)行時(shí)以及調(diào)用棧的概述原文譯者隨著變得越來越流行,團(tuán)隊(duì)在多個(gè)層級(jí)都對(duì)它進(jìn)行利用前端,后端,混合應(yīng)用,嵌入式設(shè)備以及更多。這個(gè)將會(huì)在是如何工作的的第二部分進(jìn)一步解釋。 How JavaScript works: an overview of the engine, the runtime, and the call stack JavaScript是如何工作的:引擎,運(yùn)...
摘要:調(diào)用棧是一種數(shù)據(jù)結(jié)構(gòu),它記錄了我們?cè)诔绦蛑械奈恢?。?dāng)從這個(gè)函數(shù)返回的時(shí)候,就會(huì)將這個(gè)函數(shù)從棧頂彈出,這就是調(diào)用棧做的事情。而且這不是唯一的問題,一旦你的瀏覽器開始處理調(diào)用棧中的眾多任務(wù),它可能會(huì)停止響應(yīng)相當(dāng)長(zhǎng)一段時(shí)間。 原文地址: https://blog.sessionstack.com... PS: 好久沒寫東西了,最近一直在準(zhǔn)備寫一個(gè)自己的博客,最后一些技術(shù)方向已經(jīng)敲定了,又可以...
摘要:本章會(huì)對(duì)語言引擎,運(yùn)行時(shí),調(diào)用棧做一個(gè)概述。調(diào)用棧只是一個(gè)單線程的編程語言,這意味著它只有一個(gè)調(diào)用棧。查看如下代碼當(dāng)引擎開始執(zhí)行這段代碼的時(shí)候,調(diào)用棧會(huì)被清空。之后,產(chǎn)生如下步驟調(diào)用棧中的每個(gè)入口被稱為堆棧結(jié)構(gòu)。 原文請(qǐng)查閱這里,本文采用知識(shí)共享署名 4.0 國(guó)際許可協(xié)議共享,BY Troland。 本系列持續(xù)更新中,Github 地址請(qǐng)查閱這里。 這是 JavaScript 工作原...
閱讀 2890·2021-08-20 09:37
閱讀 1616·2019-08-30 12:47
閱讀 1101·2019-08-29 13:27
閱讀 1692·2019-08-28 18:02
閱讀 757·2019-08-23 18:15
閱讀 3094·2019-08-23 16:51
閱讀 938·2019-08-23 14:13
閱讀 2156·2019-08-23 13:05