Централизованная обработка исключений в Node.JS. Часть 2



    Четыре месяца назад я писал о том, как можно удобно ловить исключения в node.js, в том числе и асинхронные, то есть те, которые брошены кодом, который вызван event loop'ом. В той статье я использовал модуль control-block для борьбы с ними, так как стандартный блок try-catch не справлялся.

    Как оказалось, примерно в то же время Adam Crabtree выпустил стабильную версию похожего на control-block модуля под названием trycatch.

    Модуль trycatch даёт некоторые дополнительные возможности, которых нет в control-block:

    1. Избавляет от необходимости дополнительно оборачивать коллбэки, переданные в setTimeout и т.д.

    Для этого модуль trycatch при загрузке подменяет функции setTimeout, setInterval, функции модуля fs, так что отпадает необходимость в постоянных вызовах Block.guard() при передаче коллбэков. Это делается автоматически.

    2. Как следствие, trycatch обеспечивает поддержку сторонних библиотек.

    Из-за необходимости оборачивания коллбэков, control-block не мог отлавливать некоторые асинхронные исключения, которые возникали в сторонних библиотеках.

    Пусть у нас есть такая неразумная сторонняя библиотека, которая не по зубам даже модулю control-block:
    function blackBox() {
      setTimeout(function() {
        throw new Error('black box error');
      }, 10);
    }
    

    А это — код, который благодаря trycatch теперь умеет c ней безопасно работать:
    var trycatch = require('trycatch');
    trycatch(function() {
      setInterval(blackBox, 1000);
    }, function(err) {
      console.log('caught!');
      console.log(err.stack);
    });
    

    Он выведет:


    3. Long stack traces

    Благодаря интеграции с модулем long-stack-traces модуль trycatch может помочь в отладке, если попросить его выводить длинные стэк трейсы, которые правильно отслеживают асинхронные исключения:
    var trycatch = require('trycatch');
    trycatch.configure({'long-stack-traces': true});
    trycatch(function() {
      setInterval(blackBox, 1000);
    }, function(err) {
      console.log('caught!');
      console.log(err.stack);
    });
    

    Этот код уже выведет такой стэк:


    К слову, до недавнего времени в trycatch предварительное составление длинного стэк трейса было обязательным, что по сравнению с control-block давало производительность примерно в 70-100 раз ниже. Я обсудил эту проблему с автором, предложил возможное решение, и вчера им была выпущена новая версия, которая теперь базируется на модуле control-block, что даёт высокую скорость работы модуля. При этом оставлена возможность включать длинные стэк трейсы, когда это необходимо, например, на сервере, где ведётся разработка, а скорость не критична. Получился этакий симбиоз, который взял от обоих проектов только самое лучшее.

    Так что несмотря на то, что модуль trycatch даже по моему мнению еще не совсем идеален, я считаю, что это лучшее решение для того, чтобы обрабатывать асинхронные исключения.
    Alawar Entertainment
    Компания

    Похожие публикации

    Реклама
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее

    Комментарии 20

      +1
      Не нужно бросать исключения в асинхронных окружениях :)

      Если есть вероятность поймать исключения, то она должна быть локализована. Типа

      function parseJSON(json, callback) {
          try {
              callaback(null, JSON.parse(json))
          } catch (e) {
              callback(e)
          }
      }
      


      И не будет никаких проблем. Разве что вы забудете повесть обработчики ошибок.
        +1
        Можно и так. Но далеко не всем это удобно, особенно принимая во внимание, что исключения могут возникать во всяких неожиданных местах. Получается, что _все_ потенциально асинхронно вызываемые функции в проекте (включая анонимные) должны начинаться блоком try, и это при том, что она не содержит логики обработки этого исключения, а просто перенаправляет его в коллбэк. Простите, но это какой-то коллбэк-ад получится.

        Во сколько строк уложится аналог этого кода: gist.github.com/4231149?
          +1
          Не все, а только те, кто способен выбросить исключения. На самом деле таких не так уж много :)

          У вас такой же callback hell, если вы в него верите, исключения передаются в коллбеки.

          И да, асинхронные функции не должны выбрасывать исключений.
            0
            > Не все, а только те, кто способен выбросить исключения.

            Про то и речь, что почти любая способна. Или вы всегда валидируете типы аргументов, наличие и типы всех полей объектов, к которым обрщаетесь, и т.д.? Дак это еще больше кода будет.

            > У вас такой же callback hell, если вы в него верите, исключения передаются в коллбеки.

            Но, к счастью, он скрыт от моих глаз и рук.

            > И да, асинхронные функции не должны выбрасывать исключений.

            Это в вас говорит религия :) А во мне говорит другая. Так что боюсь, мы с вами по этому вопросу не сможем прийти к общему знаменателю. В мире моеко текущего проекта исключения используются на полную мощность. И благодаря этим модулям, это не требует большого внимания с моей стороны.
              +1
              Не все, а только те, кто способен выбросить исключения. На самом деле таких не так уж много :)

              Решил поискать слово throw в исходниках стандартных модулей. Так вот 36 модулей из 25 содержат функции/методы выбрасывающие исключения.
              Мне тоже больше нравится в своем коде передавать ошибку первым параметром callback, но вызов любой внешней функции потенциально может завершиться исключением.
            0
            Кстати, даже эта функция не гарантирует, что она не выкинет исключение :)
            0
            С исключениями в V8 надо быть очень осторожными. Разработчики из гугла на одной из конференций говорили, что движок очень плохо оптимизирует код в try { } блоке. Поэтому лучше оборачивать в try-catch только те куски кода, где действительно может появится исключения.
              0
              Буду признателен, если сможете вспомнить ссылочку.
              0
              Судя по ссылке blog.dubbelboer.com/2012/04/16/node-try-catch.html, если блок try catch вынесен за пределы функции, этой проблемы не наблюдается. Так что это скорее должно быть адресовано комментариям bobrik'а, а не самой статье. Или я что-то не упустил?
                +1
                Вот любопытная статистика по браузерам — jsperf.com/try-catch-performance-overhead. В целом как вы и говорите, можно вынести за пределы функции и почти не потерять в производительности.
              0
              Новость интересная, но я разделяю то мнение, которое bobrik высказал.

              Всякий автор асинхронной функции вместо «throw new Error('…')» может (и должен, должен непременно!) использовать «callback (new Error('…')); return», и тем невозбранно достигнуть желаемого.
                0
                Мне хотелось бы понять пару вещей:
                Как гарантировать, что функция не выкинет exception и как это компактно записать?
                Как добиться того же самого при работе со сторонними библиотеками?
                  0
                  И желательно, чтобы это минимально сказывалось на производительности, как упомянуто выше.
                  +1
                  Я просто оставлю эту ссылку тут: nodejs.org/api/domain.html
                    0
                    Я давал эту же ссылку в первой части статьи.
                    А вы ими пользуетесь?
                      +1
                      Это конечно хорошо, но Stability: 1 — Experimental
                        0
                        Домены — веселая штука.
                        1) Требуют костылей для отлова синхронных исключений. github.com/joyent/node/issues/4770
                        2) Требуют костылей для правильной работы вложенных доменов: github.com/joyent/node/issues/4733
                        Тем не менее, автор модуля trycatch перешел на их использование.
                        0
                        Лучше всего не выдумывать всякие сложные конструкции, а считать исключения в асинхронном коде критической ошибкой, всегда роняющей приложение (каковой они по умолчанию и являются) и лечить не симптомы, а причину — исправлять код.

                        А с падающим из-за исключения приложением поступать так же, как с приложением, падающим по любой другой причине: отсутствие памяти, segfault, отключение питания на сервере,…

                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.