Домен
Этот модуль находится в стадии устаревания. После завершения разработки замены 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) {
// In real life, you'd probably use more than just 2 workers,
// and perhaps not put the master and worker in the same file.
//
// You can also of course get a bit fancier about logging, and
// implement whatever custom logic you need 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}`);
// Note: 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 isn't important. Just an example routing thing.
// You'd put your fancy application logic here.
function handleRequest(req, res) {
switch (req.url) {
case '/error':
// We do some async stuff, and then...
setTimeout(() => {
// Whoops!
flerb.bark();
});
break;
default:
res.end('ok');
}
}
Добавления к объектам ошибок
Всякий раз, когда объект Error обрабатывается через домен, к нему добавляются несколько дополнительных полей.
-
error.domainДомен, который первым обработался ошибку. -
error.domainEmitterЭмиттер событий, который испустил событие'error'с объектом ошибки. -
error.domainBoundФункция обратного вызова, которая была связана с доменом и получила ошибку в качестве первого аргумента. -
error.domainThrownЛогический флаг, указывающий, была ли ошибка вызвана, излучена или передана связанной функции обратного вызова.
Неявная привязка
Если используются домены, то все новые объекты EventEmitter (включая объекты Stream, запросы, ответы и т. д.) будут неявным образом привязаны к активному домену в момент их создания.
Кроме того, обратные вызовы, переданные в запросы цикла событий низкого уровня (например, в fs.open или другие методы, принимающие обратные вызовы), будут автоматически привязаны к активному домену. Если они выбросят ошибку, домен перехватит ошибку.
Для предотвращения чрезмерного использования памяти объекты Domain сами по себе не добавляются неявным образом как потомки активного домена. Если бы это было так, то было бы слишком легко предотвратить правильную сборку мусора для объектов запроса и ответа.
Если вы хотите вложенные объекты Domain в качестве потомков родительского Domain, то вы должны добавить их явно.
Неявная привязка перенаправляет выброшенные ошибки и события 'error' в событие 'error' домена, но не регистрирует EventEmitter в домене, поэтому domain.dispose() не завершит EventEmitter. Неявная привязка обрабатывает только выброшенные ошибки и события '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>
Возвращает новый объект Domain.
Класс: Domain
Класс Domain инкапсулирует функциональность перенаправления ошибок и необработанных исключений в активный объект Domain.
Domain является дочерним классом EventEmitter. Для обработки перехваченных ошибок, прослушивайте событие 'error'.
domain.run(fn[, arg][, ...])
-
fn<Функция>
Выполняет переданную функцию в контексте домена, неявным образом привязывает все эмиттеры событий, таймеры и запросы низкого уровня, которые создаются в этом контексте. Дополнительно аргументы могут быть переданы функции.
Это самый базовый способ использования домена.
Пример:
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') будет вызван вместо аварийного завершения программы.
domain.members
Массив таймеров и эмиттеров событий, которые были явно добавлены в домен.
domain.add(emitter)
-
emitter<EventEmitter> | <Таймер> эмиттер или таймер для добавления в домен
Явно добавляет эмиттер в домен. Если какие-либо обработчики событий, вызываемые эмиттером, вызывают ошибку или если эмиттер излучает событие 'error', оно будет перенаправлено на событие 'error' домена, как и при неявной привязке.
Это также работает с таймерами, которые возвращаются из setInterval() и setTimeout(). Если их функция обратного вызова вызовет ошибку, она будет перехвачена обработчиком ошибок домена.
Если таймер или EventEmitter уже был привязан к домену, он удаляется из него и привязывается к этому домену.
domain.remove(emitter)
-
emitter<EventEmitter> | <Таймер> эмиттер или таймер для удаления из домена
Обратное domain.add(emitter). Удаляет обработку домена от указанного эмиттера.
domain.bind(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.intercept(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.enter()
Метод enter используется модулем run, bind, и intercept для установки активного домена. Он устанавливает domain.active и process.domain в домен и неявным образом добавляет домен в стек домена, управляемый модулем домена (см. domain.exit() для получения подробной информации о стеке доменов). Вызов enter определяет начало цепочки асинхронных вызовов и операций ввода-вывода, привязанных к домену.
Вызов enter изменяет только активный домен и не изменяет сам домен. enter и exit могут быть вызваны произвольное число раз для одного домена.
Если домен, на котором вызван enter, был удалён, enter вернётся без установки домена.
domain.exit()
Метод exit выходит из текущего домена, удаляя его из стека домена. Каждый раз, когда выполнение переключается в контекст другой цепочки асинхронных вызовов, важно убедиться, что текущий домен завершен. Вызов exit определяет конец или прерывание цепочки асинхронных вызовов и операций ввода-вывода, привязанных к домену.
Если существует несколько вложенных доменов, привязанных к текущему контексту выполнения, exit выйдет из всех вложенных доменов внутри этого домена.
Вызов exit изменяет только активный домен и не изменяет сам домен. enter и exit могут быть вызваны произвольное число раз для одного домена.
Если домен, на котором вызван exit, был удалён, exit вернётся без выхода из домена.
domain.dispose()
После того, как dispose был вызван, домен больше не будет использоваться обратными вызовами, привязанными к домену через run, bind, или intercept, и событие 'dispose' излучается.
© 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-v4.x/docs/api/domain.html