Node.js v20.11.1 文档


#

¥Domain

稳定性: 0 - 已弃用

¥Stability: 0 - Deprecated

源代码: lib/domain.js

该模块正在等待弃用。一旦替代 API 完成,则该模块将被完全弃用。大多数开发者不应该有理由使用这个模块。绝对必须拥有域提供的功能的用户可能暂时依赖它,但预计将来必须迁移到不同的解决方案。

¥This module is pending deprecation. Once a replacement API has been finalized, this module will be fully deprecated. Most developers should not have cause to use this module. Users who absolutely must have the functionality that domains provide may rely on it for the time being but should expect to have to migrate to a different solution in the future.

域提供了一种将多个不同的 IO 操作作为一组来处理的方法。如果任何注册到域的事件触发器或回调触发 'error' 事件,或抛出错误,则将通知域对象,而不是丢失 process.on('uncaughtException') 句柄中的错误上下文,或导致程序立即使用错误码退出。

¥Domains provide a way to handle multiple different IO operations as a single group. If any of the event emitters or callbacks registered to a domain emit an 'error' event, or throw an error, then the domain object will be notified, rather than losing the context of the error in the process.on('uncaughtException') handler, or causing the program to exit immediately with an error code.

警告:不要忽视错误!#

¥Warning: Don't ignore errors!

发生错误时,域错误句柄不能替代关闭进程。

¥Domain error handlers are not a substitute for closing down a process when an error occurs.

根据 throw 在 JavaScript 中的工作方式的本质,几乎没有任何方法可以在不泄漏引用或创建某种其他类型的未定义的脆弱状态的情况下安全地 "从停止的地方继续"。

¥By the very nature of how throw works in JavaScript, there is almost never any way to safely "pick up where it left off", without leaking references, or creating some other sort of undefined brittle state.

响应抛出的错误最安全的方法是关闭进程。但是,在正常的 web 服务器中,可能有很多打开的连接,因为别人触发了错误而突然关闭这些连接是不合理的。

¥The safest way to respond to a thrown error is to shut down the process. Of course, in a normal web server, there may be many open connections, and it is not reasonable to abruptly shut those down because an error was triggered by someone else.

更好的方法是向触发错误的请求发送错误响应,同时让其他人在正常时间完成,并停止在该工作进程中监听新的请求。

¥The better approach is to send an error response to the request that triggered the error, while letting the others finish in their normal time, and stop listening for new requests in that worker.

通过这种方式,domain 的使用与集群模块齐头并进,因为当工作进程遇到错误时,主进程可以衍生新的工作进程。对于扩展到多台机器的 Node.js 程序,终止的代理或服务仓库可以记录故障,并做出相应的反应。

¥In this way, domain usage goes hand-in-hand with the cluster module, since the primary process can fork a new worker when a worker encounters an error. For Node.js programs that scale to multiple machines, the terminating proxy or service registry can take note of the failure, and react accordingly.

例如,这不是一个好主意:

¥For example, this is not a good idea:

// 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);
}); 

通过使用域的上下文,以及将我们的程序分成多个工作进程的弹性,我们可以做出更适当的反应,并以更高的安全性处理错误。

¥By using the context of a domain, and the resilience of separating our program into multiple worker processes, we can react more appropriately, and handle errors with much greater safety.

// 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 对象的添加#

¥Additions to Error objects

每当 Error 对象通过域路由时,都会向其中添加一些额外的字段。

¥Any time an Error object is routed through a domain, a few extra fields are added to it.

  • error.domain 最先处理错误的域。

    ¥error.domain The domain that first handled the error.

  • error.domainEmitter 触发带有错误对象的 'error' 事件的事件触发器。

    ¥error.domainEmitter The event emitter that emitted an 'error' event with the error object.

  • error.domainBound 绑定到域的回调函数,并将错误作为其第一个参数传递。

    ¥error.domainBound The callback function which was bound to the domain, and passed an error as its first argument.

  • error.domainThrown 一个布尔值,指示错误是被抛出、触发还是传递给绑定的回调函数。

    ¥error.domainThrown A boolean indicating whether the error was thrown, emitted, or passed to a bound callback function.

隐式绑定#

¥Implicit binding

如果正在使用域,则所有新的 EventEmitter 对象(包括 Stream 对象、请求、响应等)将在创建时隐式绑定到活动域。

¥If domains are in use, then all new EventEmitter objects (including Stream objects, requests, responses, etc.) will be implicitly bound to the active domain at the time of their creation.

此外,传递给底层事件循环请求(例如 fs.open() 或其他回调获取方法)的回调将自动绑定到活动域。如果它们抛出,则域将捕获错误。

¥Additionally, callbacks passed to low-level event loop requests (such as to fs.open(), or other callback-taking methods) will automatically be bound to the active domain. If they throw, then the domain will catch the error.

为了防止过多的内存使用,Domain 对象本身没有被隐式地添加为活动域的子域。如果是这样,则阻止请求和响应对象被正确地垃圾收集就太容易了。

¥In order to prevent excessive memory usage, Domain objects themselves are not implicitly added as children of the active domain. If they were, then it would be too easy to prevent request and response objects from being properly garbage collected.

要将 Domain 对象嵌套为父 Domain 的子对象,则必须显式地添加它们。

¥To nest Domain objects as children of a parent Domain they must be explicitly added.

隐式的绑定路由向 Domain'error' 事件抛出错误和 'error' 事件,但没有在 Domain 上注册 EventEmitter。隐式的绑定只处理抛出的错误和 'error' 事件。

¥Implicit binding routes thrown errors and 'error' events to the Domain's 'error' event, but does not register the EventEmitter on the Domain. Implicit binding only takes care of thrown errors and 'error' events.

显式绑定#

¥Explicit binding

有时,使用的域不是应该用于特定事件触发器的域。或者,事件触发器可以在域的上下文中创建,但可以绑定到其他域。

¥Sometimes, the domain in use is not the one that ought to be used for a specific event emitter. Or, the event emitter could have been created in the context of one domain, but ought to instead be bound to some other domain.

例如,可能有一个域用于 HTTP 服务器,但也许我们希望为每个请求使用单独的域。

¥For example, there could be one domain in use for an HTTP server, but perhaps we would like to have a separate domain to use for each request.

这可以通过显式绑定来实现。

¥That is possible via explicit binding.

// 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#

¥Class: Domain

Domain 类封装了路由错误和未捕获异常到活动 Domain 对象的功能。

¥The Domain class encapsulates the functionality of routing errors and uncaught exceptions to the active Domain object.

要处理其捕获的错误,则监听其 'error' 事件。

¥To handle the errors that it catches, listen to its 'error' event.

domain.members#

已显式地添加到域的定时器和事件触发器数组。

¥An array of timers and event emitters that have been explicitly added to the domain.

domain.add(emitter)#

显式地添加触发器到域中。如果触发器调用的任何事件句柄抛出错误,或者触发器触发 'error' 事件,则它将被路由到域的 'error' 事件,就像隐式绑定一样。

¥Explicitly adds an emitter to the domain. If any event handlers called by the emitter throw an error, or if the emitter emits an 'error' event, it will be routed to the domain's 'error' event, just like with implicit binding.

这也适用于从 setInterval()setTimeout() 返回的定时器。如果其回调函数抛出异常,则其将被域 'error' 句柄捕获。

¥This also works with timers that are returned from setInterval() and setTimeout(). If their callback function throws, it will be caught by the domain 'error' handler.

如果定时器或 EventEmitter 已绑定到某个域,则将其从该域中删除,并改为绑定到该域。

¥If the Timer or EventEmitter was already bound to a domain, it is removed from that one, and bound to this one instead.

domain.bind(callback)#

返回的函数将是提供的回调函数的封装器。当调用返回的函数时,抛出的任何错误都会被路由到域的 'error' 事件。

¥The returned function will be a wrapper around the supplied callback function. When the returned function is called, any errors that are thrown will be routed to the domain's 'error' event.

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.activeprocess.domain 设置为域,并将域隐式推送到域模块管理的域堆栈上(有关域堆栈的详细信息,请参见 domain.exit())。对 enter() 的调用界定了一系列异步调用和绑定到域的 I/O 操作的开始。

¥The enter() method is plumbing used by the run(), bind(), and intercept() methods to set the active domain. It sets domain.active and process.domain to the domain, and implicitly pushes the domain onto the domain stack managed by the domain module (see domain.exit() for details on the domain stack). The call to enter() delimits the beginning of a chain of asynchronous calls and I/O operations bound to a domain.

调用 enter() 只改变活动域,不改变域本身。enter()exit() 可以在单个域上调用任意次数。

¥Calling enter() changes only the active domain, and does not alter the domain itself. enter() and exit() can be called an arbitrary number of times on a single domain.

domain.exit()#

exit() 方法退出当前域,将其从域堆栈中弹出。任何时候执行将切换到不同异步调用链的上下文,确保退出当前域很重要。对 exit() 的调用界定了异步调用链和绑定到域的 I/O 操作链的结束或中断。

¥The exit() method exits the current domain, popping it off the domain stack. Any time execution is going to switch to the context of a different chain of asynchronous calls, it's important to ensure that the current domain is exited. The call to exit() delimits either the end of or an interruption to the chain of asynchronous calls and I/O operations bound to a domain.

如果有多个嵌套域绑定到当前执行上下文,则 exit() 将退出任何嵌套在该域中的域。

¥If there are multiple, nested domains bound to the current execution context, exit() will exit any domains nested within this domain.

调用 exit() 只改变活动域,不改变域本身。enter()exit() 可以在单个域上调用任意次数。

¥Calling exit() changes only the active domain, and does not alter the domain itself. enter() and exit() can be called an arbitrary number of times on a single domain.

domain.intercept(callback)#

此方法和 domain.bind(callback) 差不多。但是,除了捕获抛出的错误外,它还会拦截作为第一个参数发送给函数的 Error 对象。

¥This method is almost identical to domain.bind(callback). However, in addition to catching thrown errors, it will also intercept Error objects sent as the first argument to the function.

这样,常见的 if (err) return callback(err); 模式可以在一个地方用单个错误句柄替换。

¥In this way, the common if (err) return callback(err); pattern can be replaced with a single error handler in a single place.

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)#

domain.add(emitter) 的反义词。从指定的触发器中删除域处理。

¥The opposite of domain.add(emitter). Removes domain handling from the specified emitter.

domain.run(fn[, ...args])#

在域上下文中运行提供的函数,隐式绑定在该上下文中创建的所有事件触发器、定时器和底层请求。可选地,参数可以传给函数。

¥Run the supplied function in the context of the domain, implicitly binding all event emitters, timers, and low-level requests that are created in that context. Optionally, arguments can be passed to the function.

这是使用域的最基本方式。

¥This is the most basic way to use a 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') 句柄,而不是使程序崩溃。

¥In this example, the d.on('error') handler will be triggered, rather than crashing the program.

域和 promise#

¥Domains and promises

从 Node.js 8.0.0 开始,promise 的句柄在调用 .then().catch() 本身的域内运行:

¥As of Node.js 8.0.0, the handlers of promises are run inside the domain in which the call to .then() or .catch() itself was made:

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) 将回调绑定到特定域:

¥A callback may be bound to a specific domain using 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' 事件。

¥Domains will not interfere with the error handling mechanisms for promises. In other words, no 'error' event will be emitted for unhandled Promise rejections.