Node.js v21.7.2 文件
- Node.js v21.7.2
- ► 目錄
-
► 索引
- 斷言測試
- 非同步內容追蹤
- 非同步掛鉤
- 緩衝區
- C++ 外掛程式
- 使用 Node-API 的 C/C++ 外掛程式
- C++ 嵌入式 API
- 子程序
- 叢集
- 命令列選項
- 主控台
- Corepack
- 加密
- 偵錯器
- 已棄用的 API
- 診斷頻道
- DNS
- Domain
- 錯誤
- 事件
- 檔案系統
- 全域變數
- HTTP
- HTTP/2
- HTTPS
- 檢查器
- 國際化
- 模組:CommonJS 模組
- 模組:ECMAScript 模組
- 模組:
node:module
API - 模組:套件
- 網路
- 作業系統
- 路徑
- 效能掛鉤
- 權限
- 程序
- Punycode
- 查詢字串
- Readline
- REPL
- 報告
- 單一可執行應用程式
- 串流
- 字串解碼器
- 測試執行器
- 計時器
- TLS/SSL
- 追蹤事件
- TTY
- UDP/資料報
- URL
- 公用程式
- V8
- VM
- WASI
- Web Crypto API
- Web Streams API
- 工作執行緒
- Zlib
- ► 其他版本
- ► 選項
Domain#
原始碼: lib/domain.js
此模組已排定要棄用。一旦替代的 API 定案,此模組將會完全棄用。大多數開發人員不應有理由使用此模組。絕對需要網域所提供功能的使用者可以暫時依賴它,但應預期未來必須移轉到不同的解決方案。
網域提供一種方式,可以將多個不同的 IO 作業視為一個群組來處理。如果註冊到網域的任何事件發射器或回呼發出 'error'
事件,或擲出錯誤,則會通知網域物件,而不是在 process.on('uncaughtException')
處理常式中遺失錯誤的內容,或導致程式立即以錯誤碼結束。
警告:不要忽略錯誤!#
網域錯誤處理常式並非在發生錯誤時關閉程式的替代方案。
根據 throw
在 JavaScript 中運作的本質,幾乎沒有任何方法可以安全地「從中斷處繼續」,而不會洩漏參考,或產生其他類型的未定義脆弱狀態。
回應擲出錯誤最安全的方式是關閉程式。當然,在正常的網路伺服器中,可能會有許多開啟的連線,而因為其他人觸發錯誤就突然關閉這些連線並非合理的做法。
更好的方法是將錯誤回應傳送給觸發錯誤的請求,同時讓其他請求在正常時間完成,並停止在該工作執行緒中偵聽新的請求。
透過這種方式,domain
的使用與叢集模組並行,因為當工作執行緒遇到錯誤時,主要程式可以分派一個新的工作執行緒。對於擴充到多部電腦的 Node.js 程式,終止代理程式或服務登錄程式可以記錄失敗,並做出相應反應。
例如,這不是個好主意
// XXX WARNING! BAD IDEA!
const d = require('node:domain').create();
d.on('error', (er) => {
// The error won't crash the process, but what it does is worse!
// Though we've prevented abrupt process restarting, we are leaking
// a lot of resources if this ever happens.
// This is no better than process.on('uncaughtException')!
console.log(`error, but oh well ${er.message}`);
});
d.run(() => {
require('node:http').createServer((req, res) => {
handleRequest(req, res);
}).listen(PORT);
});
透過使用網域的內容,以及將程式分隔成多個工作執行緒程序的復原能力,我們可以做出更適當的反應,並以更高的安全性處理錯誤。
// Much better!
const cluster = require('node:cluster');
const PORT = +process.env.PORT || 1337;
if (cluster.isPrimary) {
// A more realistic scenario would have more than 2 workers,
// and perhaps not put the primary and worker in the same file.
//
// It is also possible to get a bit fancier about logging, and
// implement whatever custom logic is needed to prevent DoS
// attacks and other bad behavior.
//
// See the options in the cluster documentation.
//
// The important thing is that the primary does very little,
// increasing our resilience to unexpected errors.
cluster.fork();
cluster.fork();
cluster.on('disconnect', (worker) => {
console.error('disconnect!');
cluster.fork();
});
} else {
// the worker
//
// This is where we put our bugs!
const domain = require('node:domain');
// See the cluster documentation for more details about using
// worker processes to serve requests. How it works, caveats, etc.
const server = require('node:http').createServer((req, res) => {
const d = domain.create();
d.on('error', (er) => {
console.error(`error ${er.stack}`);
// We're in dangerous territory!
// By definition, something unexpected occurred,
// which we probably didn't want.
// Anything can happen now! Be very careful!
try {
// Make sure we close down within 30 seconds
const killtimer = setTimeout(() => {
process.exit(1);
}, 30000);
// But don't keep the process open just for that!
killtimer.unref();
// Stop taking new requests.
server.close();
// Let the primary know we're dead. This will trigger a
// 'disconnect' in the cluster primary, and then it will fork
// a new worker.
cluster.worker.disconnect();
// Try to send an error to the request that triggered the problem
res.statusCode = 500;
res.setHeader('content-type', 'text/plain');
res.end('Oops, there was a problem!\n');
} catch (er2) {
// Oh well, not much we can do at this point.
console.error(`Error sending 500! ${er2.stack}`);
}
});
// Because req and res were created before this domain existed,
// we need to explicitly add them.
// See the explanation of implicit vs explicit binding below.
d.add(req);
d.add(res);
// Now run the handler function in the domain.
d.run(() => {
handleRequest(req, res);
});
});
server.listen(PORT);
}
// This part is not important. Just an example routing thing.
// Put fancy application logic here.
function handleRequest(req, res) {
switch (req.url) {
case '/error':
// We do some async stuff, and then...
setTimeout(() => {
// Whoops!
flerb.bark();
}, timeout);
break;
default:
res.end('ok');
}
}
新增至 Error
物件#
任何時候 Error
物件透過網域路由,都會新增幾個額外欄位。
error.domain
最先處理錯誤的網域。error.domainEmitter
發出包含錯誤物件的'error'
事件的事件發射器。error.domainBound
與網域綁定的 callback 函式,並將錯誤作為其第一個參數傳遞。error.domainThrown
布林值,表示錯誤是否已拋出、發出或傳遞給綁定的 callback 函式。
內隱繫結#
如果網域正在使用,則所有新的 EventEmitter
物件(包括串流物件、要求、回應等)將在建立時內隱繫結至當時的活動網域。
此外,傳遞給低階事件迴圈要求的 callback(例如傳遞給 fs.open()
或其他採取 callback 的方法)將自動繫結至活動網域。如果它們拋出錯誤,網域將會捕捉該錯誤。
為了防止過度使用記憶體,Domain
物件本身不會內隱新增為活動網域的子物件。如果新增,則會過於容易防止要求和回應物件被適當地垃圾回收。
若要將 Domain
物件巢狀為父 Domain
的子物件,必須明確新增。
內隱繫結將拋出的錯誤和 'error'
事件路由至 Domain
的 'error'
事件,但不會在 Domain
上註冊 EventEmitter
。內隱繫結僅處理拋出的錯誤和 'error'
事件。
明確繫結#
有時,正在使用的網域並非特定事件發射器應使用的網域。或者,事件發射器可能是在一個網域的環境中建立的,但應改繫結到其他網域。
例如,HTTP 伺服器可能使用一個網域,但我們可能希望為每個要求使用一個獨立的網域。
這可透過明確繫結來實現。
// Create a top-level domain for the server
const domain = require('node:domain');
const http = require('node:http');
const serverDomain = domain.create();
serverDomain.run(() => {
// Server is created in the scope of serverDomain
http.createServer((req, res) => {
// Req and res are also created in the scope of serverDomain
// however, we'd prefer to have a separate domain for each request.
// create it first thing, and add req and res to it.
const reqd = domain.create();
reqd.add(req);
reqd.add(res);
reqd.on('error', (er) => {
console.error('Error', er, req.url);
try {
res.writeHead(500);
res.end('Error occurred, sorry.');
} catch (er2) {
console.error('Error sending 500', er2, req.url);
}
});
}).listen(1337);
});
domain.create()
#
- 傳回:<Domain>
類別:Domain
#
Domain
類別封裝將錯誤和未捕捉到的例外路由到作用中 Domain
物件的功能。
若要處理它捕捉到的錯誤,請聆聽它的 'error'
事件。
domain.members
#
已明確新增到網域的計時器和事件發射器的陣列。
domain.add(emitter)
#
emitter
<EventEmitter> | <Timer> 要新增到網域的發射器或計時器
明確將發射器新增到網域。如果發射器呼叫的任何事件處理常式擲回錯誤,或者如果發射器發出 'error'
事件,它將會路由到網域的 'error'
事件,就像隱式繫結一樣。
這也適用於從 setInterval()
和 setTimeout()
回傳的計時器。如果其回呼函式擲回例外,將會被網域 'error'
處理常式捕捉。
如果計時器或 EventEmitter
已繫結到網域,它會從該網域移除,並繫結到這個網域。
domain.bind(callback)
#
callback
<Function> 回呼函式- 傳回:<Function> 繫結的函式
傳回的函式會是所提供回呼函式的包裝器。當呼叫傳回的函式時,任何擲回的錯誤都會路由到網域的 'error'
事件。
const d = domain.create();
function readSomeFile(filename, cb) {
fs.readFile(filename, 'utf8', d.bind((er, data) => {
// If this throws, it will also be passed to the domain.
return cb(er, data ? JSON.parse(data) : null);
}));
}
d.on('error', (er) => {
// An error occurred somewhere. If we throw it now, it will crash the program
// with the normal line number and stack message.
});
domain.enter()
#
enter()
方法是 run()
、bind()
和 intercept()
方法用來設定主動網域的管道。它將 domain.active
和 process.domain
設定為網域,並隱含地將網域推入網域模組管理的網域堆疊(有關網域堆疊的詳細資訊,請參閱 domain.exit()
)。呼叫 enter()
界定繫結到網域的非同步呼叫和 I/O 作業鏈的開頭。
呼叫 enter()
只會變更主動網域,而不會變更網域本身。可以在單一網域上任意次數呼叫 enter()
和 exit()
。
domain.exit()
#
exit()
方法退出目前的網域,將其從網域堆疊中彈出。任何時候執行要切換到不同非同步呼叫鏈的內容時,務必確保已退出目前的網域。呼叫 exit()
界定繫結到網域的非同步呼叫和 I/O 作業鏈的結尾或中斷。
如果有多個嵌套網域繫結到目前的執行內容,exit()
會退出嵌套在此網域內的任何網域。
呼叫 exit()
只會變更 active domain,不會變更 domain 本身。enter()
和 exit()
可以針對單一 domain 呼叫任意次數。
domain.intercept(callback)
#
callback
<Function> 回呼函式- 傳回:<Function> 被攔截的函式
此方法與 domain.bind(callback)
幾乎相同。不過,除了捕捉拋出的錯誤之外,它還會攔截傳送給函式第一個引數的 Error
物件。
這樣一來,常見的 if (err) return callback(err);
模式就可以用單一錯誤處理常式取代。
const d = domain.create();
function readSomeFile(filename, cb) {
fs.readFile(filename, 'utf8', d.intercept((data) => {
// Note, the first argument is never passed to the
// callback since it is assumed to be the 'Error' argument
// and thus intercepted by the domain.
// If this throws, it will also be passed to the domain
// so the error-handling logic can be moved to the 'error'
// event on the domain instead of being repeated throughout
// the program.
return cb(null, JSON.parse(data));
}));
}
d.on('error', (er) => {
// An error occurred somewhere. If we throw it now, it will crash the program
// with the normal line number and stack message.
});
domain.remove(emitter)
#
emitter
<EventEmitter> | <Timer> 要從 domain 中移除的事件發射器或計時器
與 domain.add(emitter)
相反。移除指定事件發射器的 domain 處理。
domain.run(fn[, ...args])
#
fn
<Function>...args
<any>
在 domain 的內容中執行提供的函式,隱含地繫結在該內容中建立的所有事件發射器、計時器和低階要求。此外,可以將引數傳遞給函式。
這是使用 domain 最基本的方式。
const domain = require('node:domain');
const fs = require('node:fs');
const d = domain.create();
d.on('error', (er) => {
console.error('Caught error!', er);
});
d.run(() => {
process.nextTick(() => {
setTimeout(() => { // Simulating some various async stuff
fs.open('non-existent file', 'r', (er, fd) => {
if (er) throw er;
// proceed...
});
}, 100);
});
});
在此範例中,d.on('error')
處理常式會觸發,而不是讓程式崩潰。
網域與承諾#
從 Node.js 8.0.0 開始,承諾的處理常式會在呼叫 .then()
或 .catch()
本身的網域中執行
const d1 = domain.create();
const d2 = domain.create();
let p;
d1.run(() => {
p = Promise.resolve(42);
});
d2.run(() => {
p.then((v) => {
// running in d2
});
});
可以使用 domain.bind(callback)
將回呼函數繫結到特定網域
const d1 = domain.create();
const d2 = domain.create();
let p;
d1.run(() => {
p = Promise.resolve(42);
});
d2.run(() => {
p.then(p.domain.bind((v) => {
// running in d1
}));
});
網域不會干擾承諾的錯誤處理機制。換句話說,對於未處理的 Promise
拒絕,不會發出 'error'
事件。