Spec-Zone.ru › Node.js 12 LTS

Область

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

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

v8.0.0

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

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
  // 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 проходит через область, к нему добавляются несколько дополнительных полей.

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

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

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

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

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

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

Неявная привязка перенаправляет сброшенные ошибки и события 'error' в событие Domain области, но не регистрирует обработчик 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-v12.x/docs/api/domain.html

Spec-Zone.ru

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