Продолжаем наш урок. Давайте немного усложним этот пример, добавив работу с файлами вот таким образом:
var domain = require('domain');
var fs = require('fs');
var d = domain.create(), server;
d.on('error', function(err) {
console.error("Domain has caught %s", err);
});
d.run(function() {
setTimeout(function() {
fs.readFile(__filename, function() {
ERROR();
});
}, 1000);
});Запускаем. Как ни странно, оно все еще работает. Почему? Как домен из данной функции перекочевал в fs.readFile(__filename, function()? Да все оттуда же. Потому что внутренняя реализация функции readFile знает про домены. Когда она запускает callback, то она запускает его в контексте того же домена. Благодаря этому все хорошо.
Рассмотрим последний пример. Создаем новый объект:
server = new http.Server();
всю эту логику, которая генерирует ошибку, поместим внутрь обработчика события:
server.on('boom', function() {
setTimeout(function() {
fs.readFile(__filename, function() {
ERROR();
});
}, 1000);
});И для того, чтобы еще больше все запутать, вынесем этот код вниз. То есть, мы создаем объект в одном месте, а ошибка будет вообще после того, как запуск в домене закончился. Обработает ли ее домен? Запускаем наш код, полностью он выглядит вот так:
var domain = require('domain');
var fs = require('fs'), http = require('http');
var d = domain.create(), server;
d.on('error', function(err) {
console.error("Domain has caught %s", err);
});
d.run(function() {
server = new http.Server();
});
server.on('boom', function() {
setTimeout(function() {
fs.readFile(__filename, function() {
ERROR();
});
}, 1000);
});
server.emit('boom');Как видим, все отлично обработалось. Каким образом эта функция узнала про домен? Все благодаря интеграции. Ведь сервер – это EventEmitter. Этот модуль знает про домены, и, когда создается любой EventEmitter, если есть текущий активный домен, то он получает ссылку на него, где ее можно вывести. добавим console.log:
server = new http.Server(); console.log(server.domain);
Запускаем. Все, есть. После того, как EventEmitter привязан к домену, любые обработчики он запускает именно в контексте этого домена. Конечно же, если сервер создается вне активного домена, то ни о каком server.domain не может идти и речи. Впрочем, с такими объектами тоже можно иметь дело, необходимо лишь добавить их домен вручную: специальный вызов add:
server = new http.Server();
d.run(function() {
d.add(server);
console.log(server.domain);
});Если мы сейчас это вызовем, то все будет хорошо, ошибка перехвачена. Единственная тонкость, которую здесь надо иметь в виду, это управление памятью. Дело в том, что если EventEmitter создан именно в контексте домена, то он получает ссылку, в данном случае server.domain. А если он был создан ранее и добавлен через add, то он получает не только сам ссылку на этот домен, но еще и домен ссылается на него. То есть, у домена есть специальный массив members, где он ссылается на все, что ссылается через add. Во всяком случае, такова текущая реализация. Соответственно, получается, что с add мы имеем двустороннюю ссылку server ↔domain. В результате получается, что память может быть очищена не от сервера в отдельности, не от домена в отдельности, а от них обоих вместе. То есть, если есть какой-то долго живущий домен, и к нему через add прибавляется много всего, то память не будет очищена, пока домен не умрет, либо пока когда это станет возможным, не будет вызов d.remove(server). Как правило, в скриптах эти штуки используют редко, обычно стараются создавать все, что нужно, внутри домена, и таких проблем не возникает.
Вернемся к примеру, который рассматривали в первой части статьи, то есть, к серверу. Перейдите на commit под названием domain_1-11:
Как вы считаете, в чем же дело? Почему ошибка в обработчике запроса вылетела и не была обработана доменом? Как это поправить? Дело в том, что сервер создан вне домена. Таким образом, handler – это обработчик события request. Так как сервер создан вне домена, то при вызове обработчика никакой домен ему не передается, и throw, как и раньше, валит весь процесс. Чтобы все было хорошо, достаточно взять этот объект server и создать его внутри вызова run:
serverDomain.run(function() {
var server = require('./server');
server.listen(3000);
});
Вот так будет все нормально, поскольку сервер привязан к домену. Проверим. Запускаем код. Вызываем Chrome и переходим по тому же url:
http://127.0.0.1:3000/
Теперь домен перехватил ошибку, ничего не упало. С другой стороны, конечно же, задача не может считаться полноценно решенной, потому что нужно что-то ответить посетителю: “извините, ошибка, приходите к нам потом”.
А как нам ответить? Здесьserver.domain перехватил ошибку, но никакой информации о том, где она, у него нет. Для того, чтобы ее получить, мы будем создавать домен отдельно для каждого запроса. Выглядит это следующим образом. Есть два файла. Первый app.js – главный, основной файл приложения:
var domain = require('domain');
var serverDomain = domain.create();
var server;
serverDomain.on('error', function(err) {
console.error("Server error", err);
if (server) server.close();
setTimeout(function () {
process.exit(1);
}, 1000).unref();
});
serverDomain.run(function() {
var http = require('http');
var handler = require('./handler');
//var database = require ('mongodb');
server = http.createServer (function(req, res) {
var reqDomain = domain.create();
reqDomain.add(req);
reqDomain.add(res);
reqDomain.on('error', function(err) {
res.statusCode = 500;
res.end("Sorry, " + err);
// ...
serverDomain.emit('error', err);
});
reqDomain.run(function() {
handler(req, res);
});
});
server.listen(3000);
});Он занимается тем, что запускает сервер, создает домены. Второй – handler.js, модуль, который занимается обработкой запросов:
var fs = require('fs');
module.exports = function handler(req, res) {
if (req.url == '/') {
fs.readFile('no-such-file', function(err, content) {
if (err) throw err; // JSON.parse("invalid!")
res.end(content);
});
} else {
res.statusCode = 404;
res.end("Not Found");
}
};Итак, из чего состоит app.js?
Мы поговорим об этом в следующей статье. До скорых встреч. Код нашего урока можно найти здесь.
Материалы для статьи взяты из следующего скринкаста.
We are looking forward to meeting you on our website blog.soshace.com


