Spec-Zone.ru › Node.js 16 LTS

Домен

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

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

v8.0.0

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

v1.4.2

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

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

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

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

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

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

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

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

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

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

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

Например, это не лучший способ:

// XXX WARNING! BAD IDEA!

const d = require('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('http').createServer((req, res) => {
    handleRequest(req, res);
  }).listen(PORT);
});

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

// Much better!

const cluster = require('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('domain');

  // See the cluster documentation for more details about using
  // worker processes to serve requests. How it works, caveats, etc.

  const server = require('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 (включая объекты потока, запросы, ответы и т. д.) неявно связываются с активным доменом во время их создания.

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

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

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

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

Явное связывание

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

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

Это возможно с помощью явного связывания.

// Create a top-level domain for the server
const domain = require('domain');
const http = require('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

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

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

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

domain.members

  • <Массив>

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

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

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

domain.remove(emitter)

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

Обратный метод domain.add(emitter). Удаляет обработку домена от указанного эмиттера.

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

  • fn <Функция>
  • ...args <любой>

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

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

const domain = require('domain');
const fs = require('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'), а не аварийная остановка программы.

Домены и промисы

Начиная с 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
  });
});

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

Домены не будут вмешиваться в механизмы обработки ошибок для промисов. Другими словами, не будет излучаться событие '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-v16.x/docs/api/domain.html

Spec-Zone.ru

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