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