国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

V8 的 Error 對象與棧追蹤的妙用

Luosunce / 581人閱讀

摘要:現狀最近在寫歡迎的時候,一直為錯誤的棧追蹤而愁。由于送入隊列的是函數,因此在的參數可以放心地使用。其次,這些函數并不是立即在中調用的,而是由專門的隊列處理代碼來調用。

本文的講述都是以 Node.js 環境為例子,而 Node.js 使用的 JavaScript 引擎是 V8,因此理論上 Chrome 也能適用,其它瀏覽器我就不清楚了。

現狀

最近在寫 Rize(歡迎 star) 的時候,一直為錯誤的棧追蹤而愁。為什么呢?這要從 Rize 的架構說起。

由于 puppeteer 的絕大多數操作和 API 是異步的,而寫異步代碼的良好寫法是用 ES2017 的 async/await 語法。

但我們都知道,async/await 實際上返回的是一個 Promise(即使你沒有顯式地 return 什么,它將是 Promise)。很明顯這樣不能達到我想要的 API 鏈式調用的效果。我總不能對著 Promise 實例操作 prototype,然后把我自己的 API 挪上去吧?

所以我使用了一個隊列來保存用戶想要進行的操作。也就是說,用戶在調用 Rize 的 API 之后,并不會(也不可能)立即執行這些操作,而是放在隊列中,等待時機適合(例如瀏覽器已經啟動或者上一個操作已經完成)才執行。由于送入隊列的是函數,因此在 push 的參數可以放心地使用 async/await

但是,一旦這些操作中出現錯誤,錯誤的定位變得十分麻煩。

下面這張圖是直接用 Node.js 運行一個腳本的結果:

下面這張圖是在 Jest 中執行一段代碼的結果:

原因是,

首先,隊列中的函數是 async function,這本來就給 debug 帶來麻煩。

其次,這些函數并不是立即在 API 中調用的,而是由專門的隊列處理代碼來調用。在錯誤發生時,V8 只能跟蹤到那段隊列處理代碼那里。

這就為用戶帶來麻煩。錯誤發生了,卻只能看著錯誤消息一點一點地去試著定位有問題的地方。

探索

為此我去閱讀了 Node.js 的官方文檔,看了 Errors 這一部分,不過似乎沒什么收獲。

后來又找到了 TJ Holowaychuk 大神寫的庫 callsite,看看能不能有用。從文檔上看,這個庫并不適合我的需求。

但我閱讀了 callsite 的源碼,源碼很短,十行不到。我在源碼發現了一些信息。

callsite 是利用 V8 的 Stack Trace API 來獲取函數調用處的一些信息,如文件名,行號等等。callsite 是如何獲取這些數據的呢?

非常簡單,就一句:

var err = new Error()

對,僅僅是 new 一個 Error 實例,而且并不是要拋出這個錯誤。

對比我們平時的代碼,通常當我們 throw 一個錯誤之后,我們能得到一些錯誤棧信息。但實際上,不需要 throw,僅僅是新建一個 Error 實例,也能讓 V8 記錄下當前的調用棧信息。

解決

既然發現這個事實,那我們可以在需要記錄調用棧的地方 new 一個 Error 實例。(千萬不要把它拋出,不然你后面的代碼就沒法執行了)

此時當前的棧信息已經被記錄下來,那么我們怎樣去使用這些信息呢?

如果用戶的代碼執行正常,那就沒什么關系了。關鍵是在發生錯誤的時候。這里要提一提的是,我的那段隊列處理代碼是帶有 try…catch 塊的,大概長這樣:

try {
  await fn()
} catch (error) {
  throw error
} finally {
  // do some stuff ...
}

你可能好奇什么要把捕捉的異常還要拋出,因為我想要的是后面的 finally 塊啊,但同時我又希望異常能繼續被拋出。

在這里,我們就要對 catch 塊做點功夫。當然這個 try…catch 塊是能夠獲取到之前新建的 Error 實例的,在這里我省略了那部分代碼。

為了方便敘述,我把之前 new 的那個 Error 實例命名為 trace,即假設 const trace = new Error()

顯然把 trace 的所有棧信息都拿過來是不適合的,因為它有一些我們并不需要的棧信息(這部分信息是位于 API 調用處以上的)。

每一個 Error 實例都有個 stack 屬性,它是一個多行字符串,我們先把它的每行分開,保存在數組中:

const stack = trace.stack!.split("
")

要注意 stack 的第一行不是棧信息,而是錯誤消息,這個不能去掉。所以:

stack.splice(1, 2)

我這里有兩行的信息是沒用的,所以刪去兩行,實際上要根據你的需要修改第二個參數。

現在可以把 trace 的棧信息替換掉實際 error 的棧信息:

error.stack = stack.join("
")
結果

現在就可以得到友好的錯誤棧信息了:

配合 Jest 就能更好地定位問題所在之處:

最后是宣傳一下我正在寫的庫 Rize(可以讓你簡單優雅地使用 puppeteer),也就是本文提到的,歡迎前往 GitHub 并 star。

博客原文在這里

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/93303.html

相關文章

  • JavaScript工作原理(一):引擎,運行時,調用堆棧

    摘要:調用棧是一種單線程編程語言,這意味著它只有一個調用棧。這就是調用棧的功能。簡單代碼示例當引擎執行這段代碼時,調用棧為空,之后運行如下每個叫做堆棧幀。調用棧就是通過堆棧幀來追蹤異常,堆棧幀基本就是調用棧出現異常時候的狀態。 概述 幾乎每個人都已經聽說過V8引擎這個概念,而且大多人都知道JavaScript是單線程的,并且使用回調隊列。 這篇文章中,我們將詳細介紹這些概念,并解釋JavaS...

    Jingbin_ 評論0 收藏0
  • 【前端進階之路】內存基本知識

    摘要:在運行腳本時,需要顯示的指定對象。大對象區每一個區域都是由一組內存頁構成的。這里是唯一擁有執行權限的內存區。換句話說,是該對象被之后所能回收到內存的總和。一旦活躍對象已被移出,則在舊的半空間中剩下的任何死亡對象被丟棄。 內存管理 本文以V8為背景 對之前的文章進行重新編輯,內容做了很多的調整,使其具有邏輯更加緊湊,內容更加全面。 1. 基礎概念 1.1 生命周期 不管什么程序語言,內存...

    Simon_Zhou 評論0 收藏0
  • JavaScript是如何工作:引擎,運行時間以及回調概述

    摘要:是如何工作的引擎,運行時以及調用棧的概述原文譯者隨著變得越來越流行,團隊在多個層級都對它進行利用前端,后端,混合應用,嵌入式設備以及更多。這個將會在是如何工作的的第二部分進一步解釋。 How JavaScript works: an overview of the engine, the runtime, and the call stack JavaScript是如何工作的:引擎,運...

    he_xd 評論0 收藏0
  • 【譯】JavaScript 如何工作:對引擎、運行時、調用堆棧概述

    摘要:調用棧是一種數據結構,它記錄了我們在程序中的位置。當從這個函數返回的時候,就會將這個函數從棧頂彈出,這就是調用棧做的事情。而且這不是唯一的問題,一旦你的瀏覽器開始處理調用棧中的眾多任務,它可能會停止響應相當長一段時間。 原文地址: https://blog.sessionstack.com... PS: 好久沒寫東西了,最近一直在準備寫一個自己的博客,最后一些技術方向已經敲定了,又可以...

    Warren 評論0 收藏0
  • JavaScript 工作原理之一-引擎,運行時,調用堆棧(譯)

    摘要:本章會對語言引擎,運行時,調用棧做一個概述。調用棧只是一個單線程的編程語言,這意味著它只有一個調用棧。查看如下代碼當引擎開始執行這段代碼的時候,調用棧會被清空。之后,產生如下步驟調用棧中的每個入口被稱為堆棧結構。 原文請查閱這里,本文采用知識共享署名 4.0 國際許可協議共享,BY Troland。 本系列持續更新中,Github 地址請查閱這里。 這是 JavaScript 工作原...

    Betta 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<