摘要:快速檢查可能告訴我們,簡單地從的域處理程序拋出將允許然后捕獲異常并執(zhí)行其自己的錯誤處理程序,雖然情況并非如此,檢查后,你會看到堆棧只包含。
域模塊剖析 可用性問題 隱式行為
開發(fā)人員可以創(chuàng)建新域,然后只需運行domain.enter(),然后,它充當將來拋出者無法觀察到的任何異常的萬能捕捉器,允許模塊作者攔截不同模塊中不相關(guān)代碼的異常,防止代碼的發(fā)起者知道自己的異常。
以下是一個間接鏈接模塊如何影響另一個模塊的示例:
// 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。這樣做與監(jiān)聽"uncaughtException"不同,因為后者明確意味著全局捕獲錯誤,另一個問題是在任何"uncaughtException"處理程序之前處理域,并阻止它們運行。
另一個問題是,如果事件發(fā)射器上沒有設(shè)置"error"處理程序,域會自動路由錯誤,對此沒有可選的插入機制,而是自動跨整個異步鏈傳播。這看起來似乎很有用,但是一旦異步調(diào)用深度為兩個或更多模塊,其中一個不包含錯誤處理程序,域的創(chuàng)建者將突然捕獲意外異常,并且拋出者的異常將被作者忽視。
以下是一個簡單的示例,說明缺少"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)手動刪除連接也不會阻止連接的錯誤被自動攔截。
困擾錯誤路由和異常處理的失敗是錯誤被冒出的不一致,以下是嵌套域如何根據(jù)它們何時發(fā)生以及不會使異常冒出的示例:
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); });
可以預(yù)期嵌套域始終保持嵌套,并始終將異常傳播到域堆棧中,或者異常永遠不會自動冒出,不幸的是,這兩種情況都會發(fā)生,導(dǎo)致可能令人困惑的行為甚至可能難以調(diào)試時序沖突。
API差距雖然基于使用EventEmitter的 API可以使用bind(),而errback風(fēng)格的回調(diào)可以使用intercept(),但是隱式綁定到活動域的替代API必須在run()內(nèi)部執(zhí)行。這意味著如果模塊作者想要使用替代那些提到的機制來支持域,則他們必須自己手動實現(xiàn)域支持,而不是能夠利用現(xiàn)有的隱式機制。
錯誤傳播如果可能的話,跨嵌套域傳播錯誤并不是直截了當?shù)?,現(xiàn)有文檔顯示了如果請求處理程序中存在錯誤,如何close() http服務(wù)器的簡單示例,它沒有解釋的是如果請求處理程序為另一個異步請求創(chuàng)建另一個域?qū)嵗?,如何關(guān)閉服務(wù)器,使用以下作為錯誤傳播失敗的簡單示例:
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"); }); }); }));
即使在域?qū)嵗糜诒镜卮鎯Φ那闆r下,也可以訪問資源,仍然無法讓錯誤繼續(xù)從d2傳播回d1??焖贆z查可能告訴我們,簡單地從d2的域"error"處理程序拋出將允許d1然后捕獲異常并執(zhí)行其自己的錯誤處理程序,雖然情況并非如此,檢查domain._stack后,你會看到堆棧只包含d2。
這可能被認為是API的失敗,但即使它確實以這種方式運行,仍然存在傳遞異??步執(zhí)行中的分支失敗的事實的問題,并且該分支中的所有進一步操作必須停止。在http請求處理程序的示例中,如果我們觸發(fā)多個異步請求,然后每個異步請求將write()的數(shù)據(jù)發(fā)送回客戶端,則嘗試將write()發(fā)送到關(guān)閉的句柄會產(chǎn)生更多錯誤,
異常資源清理以下腳本包含在給定連接或其任何依賴項中發(fā)生異常的情況下在小資源依賴關(guān)系樹中正確清理的更復(fù)雜示例,將腳本分解為基本操作:
"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) { } });
當新連接發(fā)生時,同時:
在文件系統(tǒng)上打開一個文件
打開管道到獨唯一的socket
異步讀取文件的塊
將塊寫入TCP連接和任何監(jiān)聽sockets
如果這些資源中的任何一個發(fā)生錯誤,請通知所有其他附加資源,他們需要清理和關(guān)閉它們
正如我們從這個例子中可以看到的,當出現(xiàn)故障時,必須采取更多措施來正確清理資源,而不是通過域API嚴格完成,所有域提供的都是異常聚合機制。即使在域中傳播數(shù)據(jù)的潛在有用能力也容易被抵消,在本例中,通過將需要的資源作為函數(shù)參數(shù)傳遞。
盡管存在意外的異常,但應(yīng)用領(lǐng)域的一個問題仍然是能夠繼續(xù)執(zhí)行(與文檔所述相反)的簡單性,這個例子證明了這個想法背后的謬論。
隨著應(yīng)用程序本身的復(fù)雜性增加,嘗試對意外異常進行適當?shù)馁Y源清理會變得更加復(fù)雜,此示例僅具有3個基本資源,并且所有資源都具有明確的依賴路徑,如果應(yīng)用程序使用共享資源或資源重用之類的東西,那么清理能力和正確測試清理工作的能力就會大大增加。
最后,就處理錯誤而言,域不僅僅是一個美化的"uncaughtException"處理程序,除了第三方更隱式和不可觀察的行為。
資源傳播域的另一個用例是使用它來沿異步數(shù)據(jù)路徑傳播數(shù)據(jù),一個問題在于,當堆棧中有多個域時(如果異步堆棧與其他模塊一起工作,則必須假定),何時期望正確的域是模糊的。此外,能夠依賴域進行錯誤處理同時還可以檢索必要的數(shù)據(jù)之間存在沖突。
下面是一個使用域沿著異步堆棧傳播數(shù)據(jù)失敗的示例:
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嘗試使用域來傳播數(shù)據(jù),可以通過在DataStream構(gòu)造函數(shù)中分配parent: domain.active來修復(fù)此示例,然后在調(diào)用用戶的回調(diào)之前通過domain.active = domain.active.data.parent恢復(fù)它。另外,"connection"回調(diào)中的DataStream實例化必須在d.run()中運行,而不是簡單地使用d.add(c),否則將沒有活動域。
簡而言之,為此祈禱有機會使用,需要嚴格遵守一套難以執(zhí)行或測試的準則。
性能問題使用域的重要威脅是開銷,使用node的內(nèi)置http基準測試http_simple.js,沒有域,它可以處理超過22,000個請求/秒。如果它在NODE_USE_DOMAINS=1下運行,那么該數(shù)字會下降到低于17,000個請求/秒,在這種情況下,只有一個全局域。如果我們編輯基準測試,那么http請求回調(diào)會創(chuàng)建一個新的域?qū)嵗?,性能會進一步下降到15,000個請求/秒。
雖然這可能不會影響僅服務(wù)于每秒幾百甚至一千個請求的服務(wù)器,但開銷量與異步請求的數(shù)量成正比,因此,如果單個連接需要連接到其他幾個服務(wù),則所有這些服務(wù)都會導(dǎo)致將最終產(chǎn)品交付給客戶端的總體延遲。
使用AsyncWrap并跟蹤在上述基準測試中調(diào)用init/pre/post/destroy的次數(shù),我們發(fā)現(xiàn)所有被調(diào)用事件的總和超過每秒170,000次,這意味著即使為每種調(diào)用增加1微秒的開銷,任何類型的設(shè)置或拆除都會導(dǎo)致17%的性能損失。
當然,這是針對基準測試的優(yōu)化方案,但我相信這演示了域等機制盡可能廉價運行的必要性。
展望未來域模塊自2014年12月以來一直被軟棄用,但尚未被刪除,因為node目前沒有提供替代功能,在撰寫本文時,正在進行構(gòu)建AsyncWrap API的工作以及為TC39準備區(qū)域的提議,在這種情況下,有適當?shù)墓δ軄硖鎿Q域,它將經(jīng)歷完全棄用周期并最終從核心中刪除。
上一篇:流中的背壓 下一篇:如何發(fā)布N-API包文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/100372.html
Node.js 指南 Node.js?是基于Chrome的V8 JavaScript引擎構(gòu)建的JavaScript運行時。 常規(guī) 關(guān)于Node.js 入門指南 輕松分析Node.js應(yīng)用程序 Docker化Node.js Web應(yīng)用程序 遷移到安全的Buffer構(gòu)造函數(shù) Node.js核心概念 阻塞與非阻塞概述 Node.js事件循環(huán)、定時器和process.nextTick() 不要阻塞事...
摘要:如何發(fā)布包使用包說明了以下步驟首先,發(fā)布非版本更新中的版本,對于,版本變?yōu)?。瀏覽發(fā)布清單確保測試演示文檔正常。因此,如果軟件包維護者選擇使用相同的標記標記軟件包的更高版本,則將收到更高版本的版本。 如何發(fā)布N-API包 使用包iotivity-node說明了以下步驟: 首先,發(fā)布非N-API版本: 更新package.json中的版本,對于iotivity-node,版本變?yōu)?....
摘要:插件開發(fā)前端掘金作者原文地址譯者插件是為應(yīng)用添加全局功能的一種強大而且簡單的方式。提供了與使用掌控異步前端掘金教你使用在行代碼內(nèi)優(yōu)雅的實現(xiàn)文件分片斷點續(xù)傳。 Vue.js 插件開發(fā) - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins譯者:jeneser Vue.js插件是為應(yīng)用添加全局功能的一種強大而且簡單的方式。插....
摘要:為了處理請求流上的錯誤,我們將錯誤記錄到并發(fā)送狀態(tài)碼以指示,但是,在實際應(yīng)用程序中,我們需要檢查錯誤以確定正確的狀態(tài)碼和消息是什么,與通常的錯誤一樣,你應(yīng)該查閱錯誤文檔。通過對象發(fā)送狀態(tài)碼和數(shù)據(jù)。 HTTP事務(wù)的剖析 本指南的目的是讓你充分了解Node.js HTTP處理的過程,我們假設(shè)你在一般意義上知道HTTP請求的工作方式,無論語言或編程環(huán)境如何,我們還假設(shè)你對Node.js Ev...
閱讀 2880·2023-04-25 22:51
閱讀 2261·2021-10-11 10:58
閱讀 3386·2019-08-30 10:49
閱讀 1946·2019-08-29 17:09
閱讀 3193·2019-08-29 10:55
閱讀 905·2019-08-26 10:34
閱讀 3629·2019-08-23 17:54
閱讀 1048·2019-08-23 16:06