Node.js v25.0.0 文件
- Node.js v25.0.0
- 目錄
-
索引
- 斷言測試
- 非同步上下文跟蹤
- 非同步鉤子
- 緩衝區
- C++ 外掛
- 使用 Node-API 的 C/C++ 外掛
- C++ 嵌入器 API
- 子程序
- 叢集
- 命令列選項
- 控制檯
- 加密
- 偵錯程式
- 已棄用的 API
- 診斷通道
- DNS
- 域
- 環境變數
- 錯誤
- 事件
- 檔案系統
- 全域性物件
- HTTP
- HTTP/2
- HTTPS
- 檢查器
- 國際化
- 模組:CommonJS 模組
- 模組:ECMAScript 模組
- 模組:
node:moduleAPI - 模組:包
- 模組:TypeScript
- 網路
- 作業系統
- 路徑
- 效能鉤子
- 許可權
- 程序
- Punycode
- 查詢字串
- 逐行讀取
- REPL
- 報告
- 單一可執行檔案應用
- SQLite
- 流
- 字串解碼器
- 測試執行器
- 定時器
- TLS/SSL
- 跟蹤事件
- TTY
- UDP/資料報
- URL
- 實用工具
- V8
- 虛擬機器
- WASI
- Web Crypto API
- Web Streams API
- 工作執行緒
- Zlib
- 其他版本
- 選項
域#
原始碼: lib/domain.js
該模組即將被棄用。 一旦替代 API 最終確定,該模組將被完全棄用。大多數開發者 不 應該有理由使用這個模組。對於絕對必須使用域所提供功能的使用者,可以暫時依賴它,但應預期未來需要遷移到不同的解決方案。
域提供了一種將多個不同的 IO 操作作為一個單獨的組來處理的方法。如果任何註冊到域的事件發射器或回撥函式觸發了一個 'error' 事件,或者丟擲了一個錯誤,那麼域物件將會收到通知,而不是在 process.on('uncaughtException') 處理程式中丟失錯誤的上下文,或者導致程式立即以一個錯誤碼退出。
警告:不要忽略錯誤!#
域錯誤處理程式不能替代在發生錯誤時關閉程序。
根據 JavaScript 中 throw 的工作原理,幾乎沒有任何方法可以安全地“從中斷的地方繼續”,而不會洩漏引用,或造成其他某種未定義的脆弱狀態。
響應丟擲錯誤的最安全方式是關閉程序。當然,在一個正常的 Web 伺服器中,可能有很多開啟的連線,因為別人觸發的一個錯誤而突然關閉這些連線是不合理的。
更好的方法是向觸發錯誤的請求傳送一個錯誤響應,同時讓其他請求在正常時間內完成,並停止在該工作程序中監聽新的請求。
透過這種方式,domain 的使用與 cluster 模組緊密相連,因為主程序可以在一個工作程序遇到錯誤時 fork 一個新的工作程序。對於擴充套件到多臺機器的 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繫結到該域的回撥函式,並且錯誤作為其第一個引數被傳入。error.domainThrown一個布林值,指示錯誤是被丟擲、觸發,還是傳遞給一個繫結的回撥函式。
隱式繫結#
如果正在使用域,那麼所有 新 的 EventEmitter 物件(包括 Stream 物件、請求、響應等)將在其建立時隱式地繫結到活動域。
此外,傳遞給低階事件迴圈請求(例如 fs.open() 或其他接受回撥的方法)的回撥函式將自動繫結到活動域。如果它們丟擲錯誤,那麼域將捕獲該錯誤。
為了防止過度的記憶體使用,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#
- 繼承自:<EventEmitter>
Domain 類封裝了將錯誤和未捕獲的異常路由到活動 Domain 物件的功能。
要處理它捕獲的錯誤,請監聽其 'error' 事件。
domain.add(emitter)#
emitter<EventEmitter> | <Timer> 要新增到域的發射器或定時器
顯式地將一個發射器新增到域。如果該發射器呼叫的任何事件處理程式丟擲錯誤,或者如果該發射器觸發了一個 'error' 事件,它將被路由到域的 'error' 事件,就像隱式繫結一樣。
這也適用於從 setInterval() 和 setTimeout() 返回的定時器。如果它們的回撥函式丟擲錯誤,它將被域的 'error' 處理程式捕獲。
如果該 Timer 或 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 模組管理的域堆疊(有關域堆疊的詳細資訊,請參見 domain.exit())。對 enter() 的呼叫界定了一個繫結到域的非同步呼叫和 I/O 操作鏈的開始。
呼叫 enter() 只改變活動域,而不會改變域本身。enter() 和 exit() 可以在單個域上呼叫任意次數。
domain.exit()#
exit() 方法退出當前域,將其從域堆疊中彈出。任何時候當執行將要切換到不同非同步呼叫鏈的上下文時,確保退出當前域是很重要的。對 exit() 的呼叫界定了繫結到域的非同步呼叫和 I/O 操作鏈的結束或中斷。
如果當前執行上下文綁定了多個巢狀的域,exit() 將退出該域內巢狀的任何域。
呼叫 exit() 只改變活動域,而不會改變域本身。enter() 和 exit() 可以在單個域上呼叫任意次數。
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.add(emitter) 相反。從指定的發射器中移除域處理。
domain.run(fn[, ...args])#
fn<Function>...args<any>
在域的上下文中執行提供的函式,隱式地繫結在該上下文中建立的所有事件發射器、定時器和低階請求。可以選擇性地將引數傳遞給該函式。
這是使用域最基本的方式。
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') 處理程式將被觸發,而不是使程式崩潰。
域和 Promise#
自 Node.js 8.0.0 起,Promise 的處理程式在呼叫 .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 的錯誤處理機制。換句話說,對於未處理的 Promise 拒絕,不會觸發 'error' 事件。