Spec-Zone.ru › Node.js 24 LTS

Домен

История
Версия Изменения
v8.8.0

У Promises, созданных в контекстах VM, больше нет свойства .domain. Однако их обработчики по-прежнему выполняются в правильном домене, а у Promises, созданных в основном контексте, свойство .domain по-прежнему есть.

v8.0.0

Теперь обработчики для Promises вызываются в домене, в котором было создано первое обещание цепочки.

v1.4.2

Устарело начиная с: v1.4.2

Стабильность: 0 — Устарело

Исходный код: lib/domain.js

Этот модуль планируется объявить устаревшим. После окончательного утверждения API-замены модуль будет полностью объявлен устаревшим. Большинству разработчиков, скорее всего, не понадобится этот модуль. Пользователи, которым абсолютно необходимы предоставляемые доменами возможности, могут пока использовать его, но в будущем им следует ожидать перехода на другое решение.

Домены позволяют обрабатывать несколько разных операций ввода-вывода как единую группу. Если какой-либо из эмиттеров событий или обратных вызовов, зарегистрированных в домене, выдаёт событие 'error' или выбрасывает ошибку, объект домена будет уведомлён об этом, вместо того чтобы потерять контекст ошибки в обработчике process.on('uncaughtException') или немедленно завершить выполнение программы с кодом ошибки.

Предупреждение: не игнорируйте ошибки!

Обработчики ошибок домена не заменяют завершение процесса при возникновении ошибки.

В силу самой природы работы throw в JavaScript почти никогда нельзя безопасно «продолжить с того места, где выполнение остановилось», не допуская утечек ссылок или не создавая иного неопределённого и хрупкого состояния.

Самый безопасный способ реагировать на выброшенную ошибку — завершить процесс. Разумеется, у обычного веб-сервера может быть много открытых соединений, и резко прерывать их из-за ошибки, вызванной кем-то другим, неразумно.

Лучше отправить ответ об ошибке на запрос, вызвавший её, позволив остальным запросам завершиться в обычном режиме, и прекратить приём новых запросов в этом рабочем процессе.

Таким образом, использование domain тесно связано с модулем cluster: основной процесс может создать новый рабочий процесс, если в одном из них возникла ошибка. В программах 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);
}); copy

Используя контекст домена и устойчивость, обеспечиваемую разделением программы на несколько рабочих процессов, мы можем более уместно реагировать на ошибки и обрабатывать их гораздо безопаснее.

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

Дополнения к объектам Error

Каждый раз, когда объект Error проходит через домен, к нему добавляется несколько дополнительных полей.

  • error.domain Домен, который первым обработал ошибку.
  • error.domainEmitter Эмиттер событий, выдавший событие 'error' с объектом ошибки.
  • error.domainBound Функция обратного вызова, привязанная к домену и получившая ошибку в качестве первого аргумента.
  • error.domainThrown Логическое значение, указывающее, была ли ошибка выброшена, выдана или передана привязанной функции обратного вызова.

Неявная привязка

Если используются домены, все новые объекты EventEmitter (включая объекты Stream, запросы, ответы и т. д.) будут неявно привязаны к активному домену на момент их создания.

Кроме того, обратные вызовы, передаваемые низкоуровневым запросам цикла событий (например, fs.open() или другим методам, принимающим обратный вызов), автоматически привязываются к активному домену. Если они выбрасывают ошибку, домен её перехватывает.

Чтобы избежать чрезмерного использования памяти, сами объекты Domain не добавляются неявно в качестве дочерних объектов активного домена. Если бы это происходило, было бы слишком легко помешать корректной сборке мусора для объектов запросов и ответов.

Чтобы вложить объекты Domain в родительский объект Domain, их необходимо добавить явно.

Неявная привязка направляет выброшенные ошибки и события 'error' к событию 'error' объекта Domain, но не регистрирует EventEmitter в объекте Domain. Неявная привязка обрабатывает только выброшенные ошибки и события '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);
}); copy

domain.create()

  • Возвращает: <Domain>

Класс: Domain

  • Расширяет: <EventEmitter>

Класс Domain инкапсулирует функциональность маршрутизации ошибок и неперехваченных исключений к активному объекту Domain.

Чтобы обрабатывать перехваченные им ошибки, подпишитесь на его событие 'error'.

domain.members

  • Тип: <Array>

Массив эмиттеров событий, явно добавленных в домен.

domain.add(emitter)

История
Версия Изменения
v9.3.0

Больше не принимает объекты таймеров.

  • emitter <EventEmitter> эмиттер, добавляемый в домен

Явно добавляет эмиттер в домен. Если обработчики событий, вызванные эмиттером, выбросят ошибку или эмиттер выдаст событие 'error', оно будет направлено в событие '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.
}); copy

domain.enter()

Метод enter() — это служебный метод, используемый методами run(), bind() и intercept() для задания активного домена. Он устанавливает domain.active и process.domain в значение домена и неявно помещает домен в стек доменов, управляемый модулем domain (подробности о стеке доменов см. в разделе domain.exit()). Вызов enter() обозначает начало цепочки асинхронных вызовов и операций ввода-вывода, привязанных к домену.

Вызов enter() меняет только активный домен и не изменяет сам домен. Методы enter() и exit() можно вызывать для одного домена произвольное количество раз.

domain.exit()

Метод exit() покидает текущий домен, извлекая его из стека доменов. При каждом переключении выполнения на контекст другой цепочки асинхронных вызовов важно убедиться, что текущий домен покинут. Вызов exit() обозначает завершение цепочки асинхронных вызовов и операций ввода-вывода, привязанных к домену, либо прерывание этой цепочки.

Если к текущему контексту выполнения привязано несколько вложенных доменов, 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.
}); copy

domain.remove(emitter)

  • emitter <EventEmitter> эмиттер, удаляемый из домена

Противоположность методу 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);
  });
}); copy

В этом примере будет вызван обработчик 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
  });
}); copy

Обратный вызов можно привязать к определённому домену с помощью метода 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
  }));
}); copy

Домены не вмешиваются в механизмы обработки ошибок обещаний. Иными словами, для необработанных отклонений Promise событие 'error' не будет выдано.

© Joyent, Inc. and other Node contributors
Licensed under the MIT License.
Node.js is a trademark of Joyent, Inc. and is used with its permission.
We are not endorsed by or affiliated with Joyent.
https://nodejs.org/dist/latest-v24.x/docs/api/domain.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API