Spec-Zone.ru › Node.js 18 LTS

Домен

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

Любые Promise объекты, созданные в контексте виртуальной машины, больше не имеют свойства .domain. Однако обработчики по-прежнему выполняются в соответствующем домене, а Promise объекты, созданные в главном контексте, всё ещё обладают свойством .domain.

v8.0.0

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

v1.4.2

Устаревшее с версии: v1.4.2

Стабильность: 0 - Устаревшее

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

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

Домены предоставляют способ обработки нескольких операций ввода-вывода как единой группы. Если любой из эмиттеров событий или обратных вызовов, зарегистрированных в домене, сгенерирует событие '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);
}); 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 (включая объекты потока, запросы, ответы и т.д.) неявно привязываются к активному домену во время их создания.

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

Для предотвращения чрезмерного использования памяти сами объекты Domain не неявно добавляются как дочерние элементы активного домена. В противном случае было бы слишком легко препятствовать правильному сбору мусора объектов запроса и ответа.

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

Неявная привязка перенаправляет выброшенные ошибки и события 'error' в событие 'error' домена Domain, но не регистрирует обработчик EventEmitter в объекте Domain. Неявная привязка обрабатывает только выброшенные ошибки и события 'error'.

Явная привязка

Иногда используется не тот домен, который должен использоваться для определённого эмиттера событий. Или эмиттер событий мог быть создан в контексте одного домена, но должен быть связан с другим.

Например, для веб-сервера может использоваться один домен, но, возможно, для каждого запроса нам хотелось бы использовать отдельный домен.

Это возможно благодаря явной привязке.

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

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

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

Для обработки пойманных ошибок, подпишитесь на событие 'error'.

domain.members

  • <Массив>

Массив таймеров и объектов EventEmitter, которые были явно добавлены в домен.

domain.add(emitter)

  • emitter <EventEmitter> или <Таймер> объект-эмиттер или таймер, который нужно добавить в домен

Явно добавляет эмиттер в домен. Если обработчики событий, вызываемые эмиттером, генерируют ошибку или эмиттер генерирует событие 'error', оно будет перенаправлено на событие 'error' домена, аналогично неявному связыванию.

Это также работает с таймерами, возвращаемыми из setInterval() и setTimeout(). Если функция обратного вызова генерирует ошибку, она будет перехвачена обработчиком домена 'error'.

Если таймер или EventEmitter уже был привязан к домену, он будет удален из него и привязан к текущему домену.

domain.bind(callback)

  • callback <Функция> Функция обратного вызова
  • Возвращает: <Функция> Привязанная функция

Возвращаемая функция будет оберткой вокруг предоставленной функции обратного вызова. При вызове возвращенной функции, любые сгенерированные ошибки будут перенаправлены на событие '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.exit() для подробностей о стеке доменов). Вызов enter() обозначает начало цепочки асинхронных вызовов и операций ввода/вывода, привязанных к домену.

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

domain.exit()

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

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

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

domain.intercept(callback)

  • callback <Функция> Функция обратного вызова
  • Возвращает: <Функция> Перехваченная функция

Этот метод почти идентичен 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 <Функция>
  • ...args <любой тип>

Выполняет переданную функцию в контексте домена, неявно привязывая все объекты EventEmitter, таймеры и низкоуровневые запросы, которые создаются в этом контексте. Опционально, можно передать аргументы в функцию.

Это самый базовый способ использования домена.

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

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

© 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-v18.x/docs/api/domain.html

Spec-Zone.ru

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