成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

V8 的 Error 對(duì)象與棧追蹤的妙用

Luosunce / 597人閱讀

摘要:現(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)。很明顯這樣不能達(dá)到我想要的 API 鏈?zhǔn)秸{(diào)用的效果。我總不能對(duì)著 Promise 實(shí)例操作 prototype,然后把我自己的 API 挪上去吧?

所以我使用了一個(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

相關(guān)文章

  • JavaScript工作原理(一):引擎,運(yùn)行時(shí),調(diào)用堆棧

    摘要:調(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...

    Jingbin_ 評(píng)論0 收藏0
  • 【前端進(jìn)階之路】?jī)?nèi)存基本知識(shí)

    摘要:在運(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)存...

    Simon_Zhou 評(píng)論0 收藏0
  • JavaScript是如何工作:引擎,運(yùn)行時(shí)間以及回調(diào)概述

    摘要:是如何工作的引擎,運(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)...

    he_xd 評(píng)論0 收藏0
  • 【譯】JavaScript 如何工作:對(duì)引擎、運(yùn)行時(shí)、調(diào)用堆棧概述

    摘要:調(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)敲定了,又可以...

    Warren 評(píng)論0 收藏0
  • JavaScript 工作原理之一-引擎,運(yùn)行時(shí),調(diào)用堆棧(譯)

    摘要:本章會(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 工作原...

    Betta 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<