16Nov

node23_2

Продолжаем наш урок. Давайте немного усложним этот пример, добавив работу с файлами вот таким образом:

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 мы имеем двустороннюю ссылку serverdomain. В результате получается, что память может быть очищена не от сервера в отдельности, не от домена в отдельности, а от них обоих вместе. То есть, если есть какой-то долго живущий домен, и к нему через 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?

Мы поговорим об этом в следующей статье. До скорых встреч. Код нашего урока можно найти здесь.

to-be-continued-series-8

Материалы для статьи взяты из следующего скринкаста.

We are looking forward to meeting you on our website soshace.com

16. Уроки Node.js. Событийный цикл, библиотека libUV. Часть 2.

На этой радостной ноте выполнение JavaScript завершается, и libUV проверяет, есть ли какие-то watcher, которые могут сработать, то есть, есть ли какие-то внутренние обработчики. Если их нет, то завершается весь процесс Node.js, завершается весь событийный цикл. Но, в данном случае, один такой watcher, а именно обработчик на порту 3000 был поставлен. Именно поэтому процесс Node.js не завершится, а временно заснет. Он будет спать до появления какой-нибудь причины ему проснуться, например, до появления новых событий ввода-вывода.

Уроки React. Урок 1, Введение.

Окунемся немного в прошлое и разберемся как вообще появился React, и зачем вообще нам нужны фреймворки. Вернемся лет на 10 назад к примеру, когда был только чистый JavaScript, зачастую он выполнял простейшие задачи, такие как валидация формы и.т.п. Ключевая концепция – это абстрагироваться от неких обыденных проблем, и решать более сложные, задачи.

2. Уроки Express.js . Логгер, Конфигурация, Шаблонизация с EJS. Часть 1.

Всем привет! Для того чтобы дальше разрабатывать это приложение, нам нужно сделать еще две важные вещи , а именно, конфигурация и логирование. Для того чтобы конфигурировать, будем использовать модуль nconf:

Leave a Reply