Область
Исходный код: 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.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' в событие '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)
Возвращаемая функция будет обёрткой вокруг предоставленной функции обратного вызова. Когда возвращаемая функция вызывается, любые возникшие ошибки будут перенаправлены на событие '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 (см. domain.exit() для подробностей о стеке доменов). Вызов enter() обозначает начало цепочки асинхронных вызовов и операций ввода-вывода, привязанных к домену.
Вызов enter() изменяет только активный домен и не изменяет сам домен. enter() и exit() могут быть вызваны любое количество раз для одного домена.
domain.exit()
Метод exit() выходит из текущего домена, удаляя его из стека доменов. Каждый раз, когда выполнение переключается на контекст другой цепочки асинхронных вызовов, важно убедиться, что текущий домен завершен. Вызов exit() обозначает конец или прерывание цепочки асинхронных вызовов и операций ввода-вывода, привязанных к домену.
Если к текущему контексту выполнения привязано несколько вложенных доменов, exit() выйдет из всех вложенных в этот домен.
Вызов exit() изменяет только активный домен и не изменяет сам домен. enter() и exit() могут быть вызваны любое количество раз для одного домена.
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.remove(emitter)
-
emitter<EventEmitter> или <Таймер> генератор или таймер, который нужно удалить из домена
Обратная операция к domain.add(emitter). Удаляет обработку домена из указанного генератора событий.
domain.run(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-v14.x/docs/api/domain.html