23. Уроки Node.js. Домены, “асинхронный try..catch”. Часть 2.
Продолжаем наш урок. Давайте немного усложним этот пример, добавив работу с файлами вот таким образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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
, то она запускает его в контексте того же домена. Благодаря этому все хорошо.
Рассмотрим последний пример. Создаем новый объект:
1 | server = new http.Server(); |
всю эту логику, которая генерирует ошибку, поместим внутрь обработчика события:
1 2 3 4 5 6 7 | server.on('boom', function() { setTimeout(function() { fs.readFile(__filename, function() { ERROR(); }); }, 1000); }); |
И для того, чтобы еще больше все запутать, вынесем этот код вниз. То есть, мы создаем объект в одном месте, а ошибка будет вообще после того, как запуск в домене закончился. Обработает ли ее домен? Запускаем наш код, полностью он выглядит вот так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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
:
1 2 | server = new http.Server(); console.log(server.domain); |
Запускаем. Все, есть. После того, как EventEmitter
привязан к домену, любые обработчики он запускает именно в контексте этого домена. Конечно же, если сервер создается вне активного домена, то ни о каком server.domain
не может идти и речи. Впрочем, с такими объектами тоже можно иметь дело, необходимо лишь добавить их домен вручную: специальный вызов add
:
1 2 3 4 5 6 7 8 9 | 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
:
1 2 3 4 | serverDomain.run(function() { var server = require('./server'); server.listen(3000); }); |
Вот так будет все нормально, поскольку сервер привязан к домену. Проверим. Запускаем код. Вызываем Chrome и переходим по тому же url
:
http://127.0.0.1:3000/
Теперь домен перехватил ошибку, ничего не упало. С другой стороны, конечно же, задача не может считаться полноценно решенной, потому что нужно что-то ответить посетителю: “извините, ошибка, приходите к нам потом”.
А как нам ответить? Здесьserver.domain
перехватил ошибку, но никакой информации о том, где она, у него нет. Для того, чтобы ее получить, мы будем создавать домен отдельно для каждого запроса. Выглядит это следующим образом. Есть два файла. Первый app.js
– главный, основной файл приложения:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | 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
, модуль, который занимается обработкой запросов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 soshace.com
No comments yet