- 断言测试
- 异步上下文跟踪
- 异步钩子
- 缓冲(Buffer)
- C++ 插件
- 使用 Node-API 的 C/C++ 插件
- C++ 嵌入 Node环境
- 子进程(Child processes)
- 集群(Cluster)
- 命令行选项
- 控制台(Console)
- 核心包(Corepack)
- 加密(Crypto)
- 调试器(Debugger)
- 已弃用的 API
- 诊断通道(Diagnostics Channel)
- 域名系统(DNS)
- 域(Domain)
- 错误(Errors)
- 事件(Events)
- 文件系统(File system)
- 全局变量(Globals)
- HTTP
- HTTP/2
- HTTPS
- 检查器(Inspector)
- 国际化
- 模块:CommonJS 模块
- 模块:ECMAScript 模块
- 模块:
node:module
API - 模块:packages 模块
- 网络(Net)
- 系统(OS)
- 路径(Path)
- 性能挂钩(Performance hooks)
- 性能挂钩(Permissions)
- 进程(Process)
- Punycode 国际化域名编码
- 查询字符串(Query strings)
- 命令行库(Readline)
- REPL 交互式编程环境
- 诊断报告
- 单个可执行应用程序
- Stream 流
- 字符串解码器
- 单元测试
- 定时器(Timers)
- 传输层安全/SSL
- 跟踪事件
- TTY
- UDP/数据报
- URL
- 实用程序
- V8
- 虚拟机
- WebAssembly
- Web加密 API(Web Crypto API)
- 网络流 API(Web Streams API)
- 工作线程(Worker threads)
- zlib
Node.js v18.18.2 文档
- Node.js v18.18.2
- ► 目录
-
►
索引
- 断言测试
- 异步上下文跟踪
- 异步钩子
- 缓冲(Buffer)
- C++ 插件
- 使用 Node-API 的 C/C++ 插件
- C++ 嵌入 Node环境
- 子进程(Child processes)
- 集群(Cluster)
- 命令行选项
- 控制台(Console)
- 核心包(Corepack)
- 加密(Crypto)
- 调试器(Debugger)
- 已弃用的 API
- 诊断通道(Diagnostics Channel)
- 域名系统(DNS)
- 域(Domain)
- 错误(Errors)
- 事件(Events)
- 文件系统(File system)
- 全局变量(Globals)
- HTTP
- HTTP/2
- HTTPS
- 检查器(Inspector)
- 国际化
- 模块:CommonJS 模块
- 模块:ECMAScript 模块
- 模块:
node:module
API - 模块:packages 模块
- 网络(Net)
- 系统(OS)
- 路径(Path)
- 性能挂钩(Performance hooks)
- 性能挂钩(Permissions)
- 进程(Process)
- Punycode 国际化域名编码
- 查询字符串(Query strings)
- 命令行库(Readline)
- REPL 交互式编程环境
- 诊断报告
- 单个可执行应用程序
- Stream 流
- 字符串解码器
- 单元测试
- 定时器(Timers)
- 传输层安全/SSL
- 跟踪事件
- TTY
- UDP/数据报
- URL
- 实用程序
- V8
- 虚拟机
- WebAssembly
- Web加密 API(Web Crypto API)
- 网络流 API(Web Streams API)
- 工作线程(Worker threads)
- zlib
- ► 其他版本
- ► 选项
目录
域#
源代码: lib/domain.js
该模块正在等待弃用。一旦替代 API 完成,该模块将被完全弃用。大多数开发人员 不应该有理由使用这个模块。绝对必须拥有域提供的功能的用户可能暂时依赖它,但应该预计将来必须迁移到不同的解决方案。
域提供了一种将多个不同 IO 操作作为一个组进行处理的方法。如果注册到域的任何事件发射器或回调发出'error'
事件,或引发错误,则域对象将收到通知,而不是丢失
process.on('uncaughtException')
处理程序,或导致程序立即退出并显示错误代码。
警告:不要忽视错误!#
域错误处理程序不能替代发生错误时关闭进程。
根据 JavaScript 中throw
工作方式的本质,几乎没有任何方法可以安全地“从中断处继续”,而不泄漏引用或创建其他某种未定义的脆弱状态。
响应抛出的错误的最安全方法是关闭进程。当然,在普通的 Web 服务器中,可能存在许多打开的连接,并且由于其他人触发了错误而突然关闭这些连接是不合理的。
更好的方法是向触发错误的请求发送错误响应,同时让其他请求在正常时间内完成,并停止侦听该工作线程中的新请求。
通过这种方式,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
绑定到域的回调函数,并传递错误作为其第一个参数。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
类封装了将错误和未捕获异常路由到事件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()
仅更改事件域,而不会更改域本身。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])
#
在域的上下文中运行提供的函数,隐式绑定在该上下文中创建的所有事件发射器、计时器和低级请求。或者,可以将参数传递给函数。
这是使用域的最基本方法。
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'
事件。