14Nov

node-24-part_1
Всем привет! Тема этого выпуска: Домены.

Домены – это возможность Node.js, который отсутствует как в обычном JavaScript, так и в JavaScript в браузерных реализациях. Домены предназначены для того, чтобы перехватывать любые асинхронные ошибки, например, если мы взглянем на сервер ниже, который мы разбирали в одном из предыдущих статей (загрузите себе код урока по ссылке для удобства), то увидим, что пока он работает – все нормально.

var http = require('http');
var fs = require('fs');

function handler(req, res) {
    if (req.url == '/') {

        fs.readFile('index.html', function(err, content) {
            if (err) {
                console.error(err);
                res.statusCode = 500;
                res.end('There is an error on the server');
                return
            }
            res.end(content);
        });

    } else {
        res.statusCode = 404;
        res.end("Not Found");
    }

}

var server = new http.createServer(handler);
server.listen(3000);

Но если где-нибудь происходит программная ошибка, скажем, вызов неизвестной функции :

 fs.readFile('index.html', function(err, content) {
            if (err) {
                
                blabla();
                
                console.error(err);
                res.statusCode = 500;
                res.end('There is an error on the server');
                return
            }
            res.end(content);


, или человек вставил throw, вместо того чтобы обрабатывать ошибку:

 fs.readFile('index.html', function(err, content) {
            
            if (err) throw err;
            res.end(content);
        });

Или программная ошибка к примеру вызов JSON.parse("invalid!")c invalid JSON которое в конечном итоге  валит весь процесс. Получается, что у нас есть сервер, к нему подключено, скажем, 1000 клиентов, во время обработки запроса одного из них возникла программная ошибка:

if (err) throw err;//JSON.parse("invalid!")

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

Из-за того, что callbacks function(err, content) вызываются асинхронно, обычно обертывание в try... catch здесь совершенно бессильно, добавим такую запись к нашему коду вместо существующей:

var server = new http.createServer(function (req, res) {
    try {
        handler(req, res);
    } catch(err) {
        //error!
    }
});
server.listen(3000);

Даже если мы вызовем обработчик внутри try... catchон сможет поймать лишь те ошибки, которые будут во время текущей работы функцией handler. А если какие-то асинхронные callbacks, то они уже сами по себе.

Теперь, когда мы уже познакомились с проблемой, поговорим о том, как она решается в Node.js. Для этого используется модуль, который называется domain. На данный момент этот модуль устарел и использовать его в текущих версиях Node.js (7.1)не рекомендуется. Наши статьи посещенные доменам будут полезны для понимания и работы с Legacy Code к примеру. Этот модуль позволяет создавать специальный объект, который как раз и называют доменом, например, serverDomain, добавим в наш server.js:

var domain = require('domain');
var serverDomain = domain.create();

В контексте домена можно запускать функции, и он перехватит любые ошибки, включая асинхронные, которые в этой функции или запущенной из нее произойдут. Для примера сделаем небольшойrefactoring текущего кода  файла server.js, сделав export сервера и запустим его из другого файла в контексте домена:

var http = require('http');
var fs = require('fs');

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");
    }

}

var server = new http.createServer(handler);
module.exports = server;

Новый запускаемый файл будет называться app.jsдобавим его в корневую директорию. Он будет создавать объект домена и подключать сервер:

var domain = require('domain');
var serverDomain = domain.create();

var server = require('./server');

serverDomain.on('error', function(err) {
    console.error("Domain has caught %s", err);
});

serverDomain.run(function() {
    server.listen(3000);
});

Сервер просто экспортируется из  файла server.js. То есть, сейчас он не запускает сервер, а просто его создает.

Дальше мы  ставим domain-обработчик в app.js

serverDomain.on('error', function(err) {
    console.error("Domain has caught %s", err);
});

Любые ошибки, которые произойдут внутри домена, будут перехвачены этой функцией. И, наконец, самое главное, запускаем сервер внутри домена специальным вызовом run:

serverDomain.run(function() {  
  server.listen(3000);  
});  

Теперь любая ошибка, которая произойдет в результате работы функции выше или функции запущенной из нее, включая обработчики, будет перехвачена. Например,  запустим проект, а затем запустим его еще раз. Так как адрес уже занят, это привело к ошибке, то есть, сервер бросил событие error:

Domain has caught Error: listen EADDRINUSE :::3000

Раньше это событие, так как нет обработчиков на error, привело бы к исключению, которое бы повалило процесс, а теперь оно успешно перехвачено доменом.

Попробуем осуществить доступ к неизвестному файлу:

fs.readFile('no-such-file', function(err, content) {

Запускаем app.js, переходим по этому адресу в Chrome:

http://127.0.0.1:3000/

Заходим.Ошибка. Вот что произошло: почему-то выпало исключение, domainпочему-то не сработал.  В чем дело? Здесь проявился один важный подводный камень доменов. Чтобы его понять и все поправить, посмотрим, как вообще домены работают, и почему именно в этой ситуации domain не справился. Разобраться с доменами будет гораздо проще, если мы вместо одного сложного примера рассмотрим несколько простых, последовательно усложняющихся. Например создадим domain.js с таким кодом (остальные файлы можно удалить):

var domain = require('domain');

var d = domain.create(), server;

d.on('error', function(err) {
    console.error("Domain has caught %s", err);
});

d.run(function() {

    ERROR();
});

Здесь в domainзапускается функция, в которой вызывается непонятно что. Запускаем. domainперехватил ошибку:

Domain has caught ReferenceError: ERROR is not defined

Как он это сделал? Очень просто. Когда функция вызывается в d.run, вокруг нее ставится неявный try catch. Соответственно, любое исключение, которое выпадает из этой функции, тут же переходит в ошибку:

d.on('error', function(err) {
    console.error("Domain has caught %s", err);
});

Это самый тривиальный случай. Рассмотрим посложнее изменив код в domain.js следующим образом:

var domain = require('domain');

var d = domain.create(), server;

d.on('error', function(err) {
    console.error("Domain has caught %s", err);
});

d.run(function() {

        setTimeout(function() {
            ERROR();
        }, 1000);
});

Здесь эта же функция с ошибкой вызывается внутри setTimeout. Проверим, работает ли. Работает! Как эта функция, которая внутри setTimeout, узнает вообще о том, что она запущена внутри домена? Ответ на этот вопрос можно дать очень просто, ели заглянуть внутрь Node.js. Дело в том, что когда функция запускается в контексте domain, то перед его запуском внутри модуля domain происходит специальный вызов:

d.enter();

а в конце – вызов,

//d.exit();

то есть, вход в domainи выход из него:

.run(function() {  
  // d.enter();   
  
  setTimeout(function() {  
      ERROR();  
  }, 1000);  
    
  // d.exit();  
}); 

А это:

setTimeout(function() {  
      ERROR();  
  }, 1000);  
  

сработает асинхронно когда-нибудь. Она получит domainза счет того, что когда мы входим в domain, то текущий объект домена становится глобальным. Выглядит это так:

d.run(function() {  
  // d.enter();   -> process.domain  

А модуль setTimeout, как и множество других модулей Node.js, которые занимаются всякими асинхронными вещами, интегрирован с доменами. То есть, внутренняя реализация setTimeout смотрит, если есть текущий активный domain, тогда она при вызове данной функции гарантирует, что этот же domain будет активен. Соответственно, если внутри функции сделать консоль:

setTimeout(function() {  
    console.error(process.domain);  
    ERROR();  
  });  
}, 1000);  

То мы его и получим, запустив domain.js:

Domain {
  domain: null,
  _events: { error: [Function] },
  _eventsCount: 1,
  _maxListeners: undefined,
  members: [] }
Domain has caught ReferenceError: ERROR is not defined

Обратим внимание, что это обычный объект, который наследует EventEmitter.

Продолжение следует!

Код урока вы сможете найти здесь.

futurecontinued

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

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

Уроки React. Урок 13. Часть 1.

Сегодня мы с Вами поговорим об асинхронных actions. Мы начнем доставать наши статьи из API. Как вы могли заметить у нас уже работает простенький API для запуска которого нужно зайти в папку simple_api, выполнить в ней к

Code Review

Code Review проводиться в назначенных парах не мене 2 месяцев с даты формирования пары для лучшего понимания проекта поверяющими сторонами.

Таблица результатов ревью здесь.

Советы по Code Review:

Leave a Reply