Spec-Zone.ru › Node.js 10 LTS

Домен

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

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

v8.0.0

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

Устойчивость: 0 - Устаревший

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

Домены обеспечивают способ обработки нескольких различных операций ввода-вывода как единой группы. Если любой из эмиттеров событий или обратных вызовов, зарегистрированных в домене, сгенерирует событие '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
  // resources like crazy 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.isMaster) {
  // A more realistic scenario would have more than 2 workers,
  // and perhaps not put the master 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 master 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 master know we're dead. This will trigger a
        // 'disconnect' in the cluster master, 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.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 encapsulates функциональность перенаправления ошибок и необработанных исключений в активный объект домена Domain.

Domain — это дочерний класс EventEmitter. Для обработки перехваченных ошибок подпишитесь на событие '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-v10.x/docs/api/domain.html

Spec-Zone.ru

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