Область
Этот модуль находится на стадии устаревания. После завершения разработки замены 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}`);
// 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 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 (включая объекты Stream, запросы, ответы и т. д.) будут неявно привязаны к активной области на момент их создания.
Кроме того, колбеки, передаваемые в низкоуровневые запросы цикла событий (например, в fs.open или другие методы, принимающие колбеки), будут автоматически привязаны к активной области. Если они вызовут ошибку, область перехватит ошибку.
Для предотвращения чрезмерного использования памяти объекты 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 — это дочерний класс EventEmitter. Для обработки перехваченных ошибок подпишитесь на событие 'error'.
domain.members
Массив таймеров и эмиттеров событий, которые были явно добавлены в область.
domain.add(emitter)
-
emitter<EventEmitter> или <Таймер> эмиттер или таймер, который нужно добавить в область
Добавляет эмиттер в область явно. Если любые обработчики событий, вызванные эмиттером, выбросят ошибку, или если эмиттер выпустит событие 'error', оно будет перенаправлено в событие 'error' области, точно так же, как при неявной привязке.
Это также работает с таймерами, возвращаемыми из setInterval() и setTimeout(). Если их функция обратного вызова выбросит ошибку, она будет перехвачена обработчиком ошибок области.
Если таймер или 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.dispose()
После вызова dispose область больше не будет использоваться колбеками, привязанными к области через run, bind, или intercept, и будет выпущено событие 'dispose'.
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.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> | <Timer> emitter or timer to be removed from the domain
The opposite of domain.add(emitter). Removes domain handling from the specified emitter.
domain.run(fn[, ...args])
-
fn<Function> -
...args<any>
Run the supplied function in the context of the domain, implicitly binding all event emitters, timers, and lowlevel requests that are created in that context. Optionally, arguments can be passed to the function.
This is the most basic way to use a domain.
Example:
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);
});
});
In this example, the d.on('error') handler will be triggered, rather than crashing the program.
Домены и промисы
Начиная с Node 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 не будет генерироваться для необработанных отклонений промисов.
© 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-v8.x/docs/api/domain.html