Подтвердить что ты не робот

Обработка ошибок в асинхронных вызовах node.js

Я новичок в node.js, хотя я довольно хорошо знаком с JavaScript в целом. Мой вопрос касается "лучших практик" в том, как обрабатывать ошибки в node.js.

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

function handleRequest(request, response) {
  try {

    if (request.url=="whatever")
      handleWhateverRequest(request, response);
    else
      throw new Error("404 not found");

  } catch (e) {
    response.writeHead(500, {'Content-Type': 'text/plain'});
    response.end("Server error: "+e.message);
  }
}

function handleWhateverRequest(request, response) {
  if (something) 
    throw new Error("something bad happened");
  Response.end("OK");
}

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

Я понимаю, что с node.js предполагается использовать неблокирующие вызовы, что, очевидно, приводит к разному количеству обратных вызовов, как в этом примере:

var sys    = require('sys'),
    fs     = require('fs');

require("http").createServer(handleRequest).listen(8124);

function handleRequest(request, response) {

  fs.open("/proc/cpuinfo", "r",
    function(error, fd) {
      if (error)
        throw new Error("fs.open error: "+error.message);

      console.log("File open.");

      var buffer = new require('buffer').Buffer(10);
      fs.read(fd, buffer, 0, 10, null,
        function(error, bytesRead, buffer) {

          buffer.dontTryThisAtHome();  // causes exception

          response.end(buffer);
        }); //fs.read

    }); //fs.open

}

Этот пример полностью уничтожит сервер, потому что исключения не вылавливаются. Моя проблема заключается в том, что я больше не могу использовать один try/catch и, таким образом, вообще не поймаю ошибок, которые могут возникнуть при обработке запроса.

Конечно, я мог бы добавить try/catch в каждом обратном вызове, но мне не нравится этот подход, потому что тогда он до программиста, что он не забывает попробовать/поймать. Для сложного сервера с множеством разных и сложных обработчиков это неприемлемо.

Я мог бы использовать глобальный обработчик исключений (предотвращая полный сбой сервера), но затем я не могу отправить ответ пользователю, так как я не знаю, какой запрос приведет к исключению. Это также означает, что запрос остается необработанным/открытым, и браузер ждет навсегда ответа.

Есть ли у кого-то хорошее твердое решение?

4b9b3361

Ответ 1

Node 0,8 вводит новую концепцию под названием "Домены". Они очень похожи на AppDomains в .net и обеспечивают способ инкапсуляции группы операций ввода-вывода. Они в основном позволяют вам обернуть вызовы обработки запросов в группе, специфичной для контекста. Если эта группа выбрасывает любые неперехваченные исключения, их можно обрабатывать и обрабатывать таким образом, чтобы вы могли получить доступ ко всей требуемой области и конкретной конкретной информации для успешного восстановления после ошибки (если это возможно).

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

Документацию можно найти по адресу: http://nodejs.org/api/domain.html

Ответ 2

Оформить обработчик uncaughtException в node.js. Он захватывает сброшенные ошибки, которые выходят за пределы цикла событий.

http://nodejs.org/docs/v0.4.7/api/process.html#event_uncaughtException_

Но не бросать ошибки всегда лучше. Вы можете просто сделать return res.end('Unabled to load file xxx');

Ответ 3

Это одна из проблем с Node прямо сейчас. Практически невозможно отследить, какой запрос вызвал ошибку, вызванную обратным вызовом.

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

Ответ 4

Я даю ответ на свой вопрос...:)

Как кажется, нет возможности вручную обманывать ошибки. Теперь я использую вспомогательную функцию, которая сама возвращает функцию, содержащую блок try/catch. Кроме того, мой собственный класс веб-сервера проверяет, вызывает ли функция обработки запроса response.end() или вспомогательная функция try/catch waitfor() (в противном случае возникает исключение). Это позволяет избежать того, что запрос ошибочно оставлен незащищенным разработчиком. Это не 100% -ное решение с ошибкой, но для меня достаточно хорошо.

handler.waitfor = function(callback) {
  var me=this;

  // avoid exception because response.end() won't be called immediately:
  this.waiting=true;

  return function() {
    me.waiting=false;
    try {
      callback.apply(this, arguments);

      if (!me.waiting && !me.finished)
        throw new Error("Response handler returned and did neither send a "+
          "response nor did it call waitfor()");

    } catch (e) {
      me.handleException(e);
    }
  }
}

Таким образом, мне просто нужно добавить встроенный вызов waitfor(), чтобы быть в безопасности.

function handleRequest(request, response, handler) {
  fs.read(fd, buffer, 0, 10, null, handler.waitfor(
    function(error, bytesRead, buffer) {

      buffer.unknownFunction();  // causes exception
      response.end(buffer);
    }
  )); //fs.read
}

Фактический механизм проверки немного сложнее, но должно быть ясно, как это работает. Если кто-то заинтересован, я могу опубликовать полный код здесь.

Ответ 5

Очень хороший вопрос. Я имею дело с той же проблемой сейчас. Вероятно, лучшим способом было бы использовать uncaughtException. Ссылка на объекты respone и request не является проблемой, потому что вы можете обернуть их в свой объект исключения, который передается в событие uncaughtException. Что-то вроде этого:

var HttpException = function (request, response, message, code) {

  this.request = request;
  this.response = response;  
  this.message = message;    
  this.code = code || 500;

}

Бросьте его:

throw new HttpException(request, response, 'File not found', 404);

И обработайте ответ:

process.on('uncaughtException', function (exception) {
  exception.response.writeHead(exception.code, {'Content-Type': 'text/html'});
  exception.response.end('Error ' + exception.code + ' - ' + exception.message);
});

Я еще не тестировал это решение, но я не вижу причин, по которым это не сработало.

Ответ 6

Одна идея: вы можете просто использовать вспомогательный метод для создания обратных вызовов и использовать его в своей стандартной практике. Это накладывает нагрузку на разработчика, но по крайней мере у вас может быть "стандартный" способ обработки ваших обратных вызовов, так что вероятность его забывания низкая:

var callWithHttpCatch = function(response, fn) {
    try {
        fn && fn();
    }
    catch {
        response.writeHead(500, {'Content-Type': 'text/plain'}); //No
    }
}

<snipped>
      var buffer = new require('buffer').Buffer(10);
      fs.read(fd, buffer, 0, 10, null,
        function(error, bytesRead, buffer) {

          callWithHttpCatch(response, buffer.dontTryThisAtHome());  // causes exception

          response.end(buffer);
        }); //fs.read

    }); //fs.open

Я знаю, что, вероятно, это не тот ответ, который вы искали, но одна из приятных вещей о ECMAScript (или функциональном программировании в целом) заключается в том, как легко вы можете использовать свой собственный инструмент для таких вещей.

Ответ 7

Во время написания этого подхода, я вижу, это использование "Promises".

http://howtonode.org/promises
https://www.promisejs.org/

Они позволяют хорошо структурировать код и обратные вызовы для управления ошибками, а также сделать его более читабельным. В основном он использует функцию .then().

someFunction().then(success_callback_func, failed_callback_func);

Вот базовый пример:

  var SomeModule = require('someModule');

  var success = function (ret) {
      console.log('>>>>>>>> Success!');
  }

  var failed = function (err) {
    if (err instanceof SomeModule.errorName) {
      // Note: I've often seen the error definitions in SomeModule.errors.ErrorName
      console.log("FOUND SPECIFIC ERROR");
    }
    console.log('>>>>>>>> FAILED!');
  }

  someFunction().then(success, failed);
  console.log("This line with appear instantly, since the last function was asynchronous.");

Ответ 8

Две вещи действительно помогли мне решить эту проблему в моем коде.

  • Модуль 'longjohn', который позволяет видеть полную трассировку стека (через несколько асинхронных обратных вызовов).
  • Простая техника закрытия для хранения исключений в стандартной идиоме callback(err, data) (показана здесь в CoffeeScript).

    ferry_errors = (callback, f) ->
      return (a...) ->
        try f(a...)
        catch err
          callback(err)
    

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

Ответ 9

Недавно я создал простую абстракцию с именем WaitFor для вызова асинхронных функций в режиме синхронизации (на основе Fibers): https://github.com/luciotato/waitfor

Это слишком ново, чтобы быть "твердым".

используя wait.for, вы можете использовать async-функцию, как если бы они были синхронизированы, без блокировки цикла событий node. Это почти то же самое, что вы привыкли:

var wait=require('wait.for');

function handleRequest(request, response) {
      //launch fiber, keep node spinning
      wait.launchFiber(handleinFiber,request, response); 
}

function handleInFiber(request, response) {
  try {
    if (request.url=="whatever")
      handleWhateverRequest(request, response);
    else
      throw new Error("404 not found");

  } catch (e) {
    response.writeHead(500, {'Content-Type': 'text/plain'});
    response.end("Server error: "+e.message);
  }
}

function handleWhateverRequest(request, response, callback) {
  if (something) 
    throw new Error("something bad happened");
  Response.end("OK");
}

Поскольку вы находитесь в волокне, вы можете последовательно программировать "блокировку волокна", но не node цикл событий.

Другой пример:

var sys    = require('sys'),
    fs     = require('fs'),
    wait   = require('wait.for');

require("http").createServer( function(req,res){
      wait.launchFiber(handleRequest,req,res) //handle in a fiber
  ).listen(8124);

function handleRequest(request, response) {
  try {
    var fd=wait.for(fs.open,"/proc/cpuinfo", "r");
    console.log("File open.");
    var buffer = new require('buffer').Buffer(10);

    var bytesRead=wait.for(fs.read,fd, buffer, 0, 10, null);

    buffer.dontTryThisAtHome();  // causes exception

    response.end(buffer);
  }
  catch(err) {
    response.end('ERROR: '+err.message);
  }

}

Как вы можете видеть, я использовал wait.for для вызова асинхронных функций node в режиме синхронизации, без видимых обратных вызовов, поэтому я могу иметь весь код внутри одного блока try-catch.

wait.for выдаст исключение, если какая-либо из функций async вернет err! == null

Дополнительная информация на https://github.com/luciotato/waitfor

Ответ 10

Также в синхронном многопоточном программировании (например,.NET, Java, PHP) вы не можете вернуть какую-либо значимую информацию клиенту, когда будет обнаружено пользовательское исключение Unkown. Вы можете просто вернуть HTTP 500, когда у вас нет информации об исключении.

Таким образом, "секрет" заключается в заполнении описательного объекта Error, таким образом ваш обработчик ошибок может отображать значимую ошибку в правильном состоянии HTTP +, необязательно, описательный результат. Однако вы также должны поймать исключение, прежде чем оно поступит в process.on('uncaughtException'):

Шаг1: определение значимого объекта ошибки

function appError(errorCode, description, isOperational) {
    Error.call(this);
    Error.captureStackTrace(this);
    this.errorCode = errorCode;
    //...other properties assigned here
};

appError.prototype.__proto__ = Error.prototype;
module.exports.appError = appError;

Шаг 2: при выбросе исключения заполните его свойствами (см. шаг 1), который позволяет обработчику преобразовать его в HTTP-результат meannigul:

throw new appError(errorManagement.commonErrors.resourceNotFound, "further explanation", true)

Шаг 3: При вызове некоторого потенциально опасного кода, уловить ошибки и повторно выбросить эту ошибку при заполнении дополнительных контекстных свойств в объекте Error

Шаг 4: вы должны поймать исключение во время обработки запроса. Это проще, если вы используете какую-то ведущую библиотеку promises (BlueBird отлично), которая позволяет вам улавливать асинхронные ошибки. Если вы не можете использовать promises, чем любая встроенная библиотека NODE вернет ошибки в обратном вызове.

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

    //this specific example is using Express framework
    res.status(getErrorHTTPCode(error))
function getErrorHTTPCode(error)
{
    if(error.errorCode == commonErrors.InvalidInput)
        return 400;
    else if...
}

Вы можете использовать другие соответствующие передовые методы здесь