摘要:快速檢查可能告訴我們,簡單地從的域處理程序拋出將允許然后捕獲異常并執行其自己的錯誤處理程序,雖然情況并非如此,檢查后,你會看到堆棧只包含。
域模塊剖析 可用性問題 隱式行為
開發人員可以創建新域,然后只需運行domain.enter(),然后,它充當將來拋出者無法觀察到的任何異常的萬能捕捉器,允許模塊作者攔截不同模塊中不相關代碼的異常,防止代碼的發起者知道自己的異常。
以下是一個間接鏈接模塊如何影響另一個模塊的示例:
// module a.js const b = require("./b"); const c = require("./c"); // module b.js const d = require("domain").create(); d.on("error", () => { /* silence everything */ }); d.enter(); // module c.js const dep = require("some-dep"); dep.method(); // Uh-oh! This method doesn"t actually exist.
由于模塊b進入域但從不退出,任何未捕獲的異常都將被吞噬,不讓模塊c知道它為什么沒有運行整個腳本,留下可能部分填充的module.exports。這樣做與監聽"uncaughtException"不同,因為后者明確意味著全局捕獲錯誤,另一個問題是在任何"uncaughtException"處理程序之前處理域,并阻止它們運行。
另一個問題是,如果事件發射器上沒有設置"error"處理程序,域會自動路由錯誤,對此沒有可選的插入機制,而是自動跨整個異步鏈傳播。這看起來似乎很有用,但是一旦異步調用深度為兩個或更多模塊,其中一個不包含錯誤處理程序,域的創建者將突然捕獲意外異常,并且拋出者的異常將被作者忽視。
以下是一個簡單的示例,說明缺少"error"處理程序如何允許活動域攔截錯誤:
const domain = require("domain"); const net = require("net"); const d = domain.create(); d.on("error", (err) => console.error(err.message)); d.run(() => net.createServer((c) => { c.end(); c.write("bye"); }).listen(8000));
即使通過d.remove(c)手動刪除連接也不會阻止連接的錯誤被自動攔截。
困擾錯誤路由和異常處理的失敗是錯誤被冒出的不一致,以下是嵌套域如何根據它們何時發生以及不會使異常冒出的示例:
const domain = require("domain"); const net = require("net"); const d = domain.create(); d.on("error", () => console.error("d intercepted an error")); d.run(() => { const server = net.createServer((c) => { const e = domain.create(); // No "error" handler being set. e.run(() => { // This will not be caught by d"s error handler. setImmediate(() => { throw new Error("thrown from setImmediate"); }); // Though this one will bubble to d"s error handler. throw new Error("immediately thrown"); }); }).listen(8080); });
可以預期嵌套域始終保持嵌套,并始終將異常傳播到域堆棧中,或者異常永遠不會自動冒出,不幸的是,這兩種情況都會發生,導致可能令人困惑的行為甚至可能難以調試時序沖突。
API差距雖然基于使用EventEmitter的 API可以使用bind(),而errback風格的回調可以使用intercept(),但是隱式綁定到活動域的替代API必須在run()內部執行。這意味著如果模塊作者想要使用替代那些提到的機制來支持域,則他們必須自己手動實現域支持,而不是能夠利用現有的隱式機制。
錯誤傳播如果可能的話,跨嵌套域傳播錯誤并不是直截了當的,現有文檔顯示了如果請求處理程序中存在錯誤,如何close() http服務器的簡單示例,它沒有解釋的是如果請求處理程序為另一個異步請求創建另一個域實例,如何關閉服務器,使用以下作為錯誤傳播失敗的簡單示例:
const d1 = domain.create(); d1.foo = true; // custom member to make more visible in console d1.on("error", (er) => { /* handle error */ }); d1.run(() => setTimeout(() => { const d2 = domain.create(); d2.bar = 43; d2.on("error", (er) => console.error(er.message, domain._stack)); d2.run(() => { setTimeout(() => { setTimeout(() => { throw new Error("outer"); }); throw new Error("inner"); }); }); }));
即使在域實例用于本地存儲的情況下,也可以訪問資源,仍然無法讓錯誤繼續從d2傳播回d1。快速檢查可能告訴我們,簡單地從d2的域"error"處理程序拋出將允許d1然后捕獲異常并執行其自己的錯誤處理程序,雖然情況并非如此,檢查domain._stack后,你會看到堆棧只包含d2。
這可能被認為是API的失敗,但即使它確實以這種方式運行,仍然存在傳遞異??步執行中的分支失敗的事實的問題,并且該分支中的所有進一步操作必須停止。在http請求處理程序的示例中,如果我們觸發多個異步請求,然后每個異步請求將write()的數據發送回客戶端,則嘗試將write()發送到關閉的句柄會產生更多錯誤,
異常資源清理以下腳本包含在給定連接或其任何依賴項中發生異常的情況下在小資源依賴關系樹中正確清理的更復雜示例,將腳本分解為基本操作:
"use strict"; const domain = require("domain"); const EE = require("events"); const fs = require("fs"); const net = require("net"); const util = require("util"); const print = process._rawDebug; const pipeList = []; const FILENAME = "/tmp/tmp.tmp"; const PIPENAME = "/tmp/node-domain-example-"; const FILESIZE = 1024; let uid = 0; // Setting up temporary resources const buf = Buffer.alloc(FILESIZE); for (let i = 0; i < buf.length; i++) buf[i] = ((Math.random() * 1e3) % 78) + 48; // Basic ASCII fs.writeFileSync(FILENAME, buf); function ConnectionResource(c) { EE.call(this); this._connection = c; this._alive = true; this._domain = domain.create(); this._id = Math.random().toString(32).substr(2).substr(0, 8) + (++uid); this._domain.add(c); this._domain.on("error", () => { this._alive = false; }); } util.inherits(ConnectionResource, EE); ConnectionResource.prototype.end = function end(chunk) { this._alive = false; this._connection.end(chunk); this.emit("end"); }; ConnectionResource.prototype.isAlive = function isAlive() { return this._alive; }; ConnectionResource.prototype.id = function id() { return this._id; }; ConnectionResource.prototype.write = function write(chunk) { this.emit("data", chunk); return this._connection.write(chunk); }; // Example begin net.createServer((c) => { const cr = new ConnectionResource(c); const d1 = domain.create(); fs.open(FILENAME, "r", d1.intercept((fd) => { streamInParts(fd, cr, 0); })); pipeData(cr); c.on("close", () => cr.end()); }).listen(8080); function streamInParts(fd, cr, pos) { const d2 = domain.create(); const alive = true; d2.on("error", (er) => { print("d2 error:", er.message); cr.end(); }); fs.read(fd, Buffer.alloc(10), 0, 10, pos, d2.intercept((bRead, buf) => { if (!cr.isAlive()) { return fs.close(fd); } if (cr._connection.bytesWritten < FILESIZE) { // Documentation says callback is optional, but doesn"t mention that if // the write fails an exception will be thrown. const goodtogo = cr.write(buf); if (goodtogo) { setTimeout(() => streamInParts(fd, cr, pos + bRead), 1000); } else { cr._connection.once("drain", () => streamInParts(fd, cr, pos + bRead)); } return; } cr.end(buf); fs.close(fd); })); } function pipeData(cr) { const pname = PIPENAME + cr.id(); const ps = net.createServer(); const d3 = domain.create(); const connectionList = []; d3.on("error", (er) => { print("d3 error:", er.message); cr.end(); }); d3.add(ps); ps.on("connection", (conn) => { connectionList.push(conn); conn.on("data", () => {}); // don"t care about incoming data. conn.on("close", () => { connectionList.splice(connectionList.indexOf(conn), 1); }); }); cr.on("data", (chunk) => { for (let i = 0; i < connectionList.length; i++) { connectionList[i].write(chunk); } }); cr.on("end", () => { for (let i = 0; i < connectionList.length; i++) { connectionList[i].end(); } ps.close(); }); pipeList.push(pname); ps.listen(pname); } process.on("SIGINT", () => process.exit()); process.on("exit", () => { try { for (let i = 0; i < pipeList.length; i++) { fs.unlinkSync(pipeList[i]); } fs.unlinkSync(FILENAME); } catch (e) { } });
當新連接發生時,同時:
在文件系統上打開一個文件
打開管道到獨唯一的socket
異步讀取文件的塊
將塊寫入TCP連接和任何監聽sockets
如果這些資源中的任何一個發生錯誤,請通知所有其他附加資源,他們需要清理和關閉它們
正如我們從這個例子中可以看到的,當出現故障時,必須采取更多措施來正確清理資源,而不是通過域API嚴格完成,所有域提供的都是異常聚合機制。即使在域中傳播數據的潛在有用能力也容易被抵消,在本例中,通過將需要的資源作為函數參數傳遞。
盡管存在意外的異常,但應用領域的一個問題仍然是能夠繼續執行(與文檔所述相反)的簡單性,這個例子證明了這個想法背后的謬論。
隨著應用程序本身的復雜性增加,嘗試對意外異常進行適當的資源清理會變得更加復雜,此示例僅具有3個基本資源,并且所有資源都具有明確的依賴路徑,如果應用程序使用共享資源或資源重用之類的東西,那么清理能力和正確測試清理工作的能力就會大大增加。
最后,就處理錯誤而言,域不僅僅是一個美化的"uncaughtException"處理程序,除了第三方更隱式和不可觀察的行為。
資源傳播域的另一個用例是使用它來沿異步數據路徑傳播數據,一個問題在于,當堆棧中有多個域時(如果異步堆棧與其他模塊一起工作,則必須假定),何時期望正確的域是模糊的。此外,能夠依賴域進行錯誤處理同時還可以檢索必要的數據之間存在沖突。
下面是一個使用域沿著異步堆棧傳播數據失敗的示例:
const domain = require("domain"); const net = require("net"); const server = net.createServer((c) => { // Use a domain to propagate data across events within the // connection so that we don"t have to pass arguments // everywhere. const d = domain.create(); d.data = { connection: c }; d.add(c); // Mock class that does some useless async data transformation // for demonstration purposes. const ds = new DataStream(dataTransformed); c.on("data", (chunk) => ds.data(chunk)); }).listen(8080, () => console.log("listening on 8080")); function dataTransformed(chunk) { // FAIL! Because the DataStream instance also created a // domain we have now lost the active domain we had // hoped to use. domain.active.data.connection.write(chunk); } function DataStream(cb) { this.cb = cb; // DataStream wants to use domains for data propagation too! // Unfortunately this will conflict with any domain that // already exists. this.domain = domain.create(); this.domain.data = { inst: this }; } DataStream.prototype.data = function data(chunk) { // This code is self contained, but pretend it"s a complex // operation that crosses at least one other module. So // passing along "this", etc., is not easy. this.domain.run(() => { // Simulate an async operation that does the data transform. setImmediate(() => { for (let i = 0; i < chunk.length; i++) chunk[i] = ((chunk[i] + Math.random() * 100) % 96) + 33; // Grab the instance from the active domain and use that // to call the user"s callback. const self = domain.active.data.inst; self.cb(chunk); }); }); };
以上顯示,很難有多個異步API嘗試使用域來傳播數據,可以通過在DataStream構造函數中分配parent: domain.active來修復此示例,然后在調用用戶的回調之前通過domain.active = domain.active.data.parent恢復它。另外,"connection"回調中的DataStream實例化必須在d.run()中運行,而不是簡單地使用d.add(c),否則將沒有活動域。
簡而言之,為此祈禱有機會使用,需要嚴格遵守一套難以執行或測試的準則。
性能問題使用域的重要威脅是開銷,使用node的內置http基準測試http_simple.js,沒有域,它可以處理超過22,000個請求/秒。如果它在NODE_USE_DOMAINS=1下運行,那么該數字會下降到低于17,000個請求/秒,在這種情況下,只有一個全局域。如果我們編輯基準測試,那么http請求回調會創建一個新的域實例,性能會進一步下降到15,000個請求/秒。
雖然這可能不會影響僅服務于每秒幾百甚至一千個請求的服務器,但開銷量與異步請求的數量成正比,因此,如果單個連接需要連接到其他幾個服務,則所有這些服務都會導致將最終產品交付給客戶端的總體延遲。
使用AsyncWrap并跟蹤在上述基準測試中調用init/pre/post/destroy的次數,我們發現所有被調用事件的總和超過每秒170,000次,這意味著即使為每種調用增加1微秒的開銷,任何類型的設置或拆除都會導致17%的性能損失。
當然,這是針對基準測試的優化方案,但我相信這演示了域等機制盡可能廉價運行的必要性。
展望未來域模塊自2014年12月以來一直被軟棄用,但尚未被刪除,因為node目前沒有提供替代功能,在撰寫本文時,正在進行構建AsyncWrap API的工作以及為TC39準備區域的提議,在這種情況下,有適當的功能來替換域,它將經歷完全棄用周期并最終從核心中刪除。
上一篇:流中的背壓 下一篇:如何發布N-API包文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/100372.html
Node.js 指南 Node.js?是基于Chrome的V8 JavaScript引擎構建的JavaScript運行時。 常規 關于Node.js 入門指南 輕松分析Node.js應用程序 Docker化Node.js Web應用程序 遷移到安全的Buffer構造函數 Node.js核心概念 阻塞與非阻塞概述 Node.js事件循環、定時器和process.nextTick() 不要阻塞事...
摘要:如何發布包使用包說明了以下步驟首先,發布非版本更新中的版本,對于,版本變為。瀏覽發布清單確保測試演示文檔正常。因此,如果軟件包維護者選擇使用相同的標記標記軟件包的更高版本,則將收到更高版本的版本。 如何發布N-API包 使用包iotivity-node說明了以下步驟: 首先,發布非N-API版本: 更新package.json中的版本,對于iotivity-node,版本變為1....
摘要:插件開發前端掘金作者原文地址譯者插件是為應用添加全局功能的一種強大而且簡單的方式。提供了與使用掌控異步前端掘金教你使用在行代碼內優雅的實現文件分片斷點續傳。 Vue.js 插件開發 - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins譯者:jeneser Vue.js插件是為應用添加全局功能的一種強大而且簡單的方式。插....
摘要:為了處理請求流上的錯誤,我們將錯誤記錄到并發送狀態碼以指示,但是,在實際應用程序中,我們需要檢查錯誤以確定正確的狀態碼和消息是什么,與通常的錯誤一樣,你應該查閱錯誤文檔。通過對象發送狀態碼和數據。 HTTP事務的剖析 本指南的目的是讓你充分了解Node.js HTTP處理的過程,我們假設你在一般意義上知道HTTP請求的工作方式,無論語言或編程環境如何,我們還假設你對Node.js Ev...
閱讀 2801·2023-04-25 22:51
閱讀 2026·2021-10-11 10:58
閱讀 3308·2019-08-30 10:49
閱讀 1870·2019-08-29 17:09
閱讀 3136·2019-08-29 10:55
閱讀 839·2019-08-26 10:34
閱讀 3467·2019-08-23 17:54
閱讀 980·2019-08-23 16:06