23. Уроки Node.js. Домены, “асинхронный try..catch”. Часть 1.

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

Domain has caught Error: listen EADDRINUSE :::3000

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

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

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

http://127.0.0.1:3000/

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

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

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

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

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

d.enter();

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

//d.exit();

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

А это:

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

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

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

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

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

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

futurecontinued

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

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

About the author

Stay Informed

It's important to keep up
with industry - subscribe!

Stay Informed

Looks good!
Please enter the correct name.
Please enter the correct email.
Looks good!

Related articles

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

Favicon – это все connect Middleware, он смотрит, если url имеет вид favicon.ico, то он читает favicon и ...

3. Уроки Express.js. Шаблонизация с EJS: Layout, Block, Partials

В реальной жизни у нас обычно больше, чем один шаблон. Более того, если уж так ...

24.11.2016

Уроки Express.js. Основы и Middleware. Часть 2.

Всем привет! Давайте продолжим наш урок об основах Express и Middleware. Итог (добавим в ...

No comments yet

Sign in

Forgot password?

Or use a social network account

 

By Signing In \ Signing Up, you agree to our privacy policy

Password recovery

You can also try to

Or use a social network account

 

By Signing In \ Signing Up, you agree to our privacy policy