Как правильно обрабатывать ошибки с помощью IcedCoffeeScript?

В node.js принято возвращать сообщение об ошибке в качестве первого аргумента функции обратного вызова. Есть несколько решений этой проблемы в чистом JS (Promise, Step, seq и т. д.), но ни одно из них не интегрируется с ICS. Что было бы правильным решением для обработки ошибок без потери читабельности?

Например:

# makes code hard to read and encourage duplication
await socket.get 'image id', defer err, id
if err # ...
await Image.findById id, defer err, image
if err # ...
await check_permissions user, image, defer err, permitted
if err # ...


# will only handle the last error
await  
  socket.get 'image id', defer err, id
  Image.findById id, defer err, image
  check_permissions user, image, defer err, permitted

if err  # ...


# ugly, makes code more rigid
# no way to prevent execution of commands if the first one failed
await  
  socket.get 'image id', defer err1, id
  Image.findById id, defer err2, image
  check_permissions user, image, defer err3, permitted

if err1 || err2 || err3  # ...

person Andrew    schedule 14.01.2013    source источник


Ответы (2)


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

my_fn = (cb) ->
  await socket.get 'image id', defer err, id
  if err then return cb err, null
  await Image.findById id, defer err, image
  if err then return cb err, null
  await check_permissions user, image, defer err, permitted
  if err then return cb err, null
  cb err, image

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

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

Мои личные соглашения по кодированию ICS: (1) возврат только один раз из функции (управление которой падает с конца); и (2) пытаться обрабатывать ошибки на одном уровне отступа. Переписывая то, что у вас есть, в моем предпочтительном стиле:

my_fn = (cb) ->
  await socket.get 'image id', defer err, id 
  await Image.findById id, defer err, image                   unless err?
  await check_permissions user, image, defer err, permitted   unless err?
  cb err, image

В случае ошибки в вызове socket.get вам нужно проверить ошибку дважды, и оба раза она, очевидно, не удастся. Я не думаю, что это конец света, так как это делает код чище.

В качестве альтернативы вы можете сделать это:

my_fn = (autocb) ->
  await socket.get 'image id', defer err, id
  if err then return [ err, null ]
  await Image.findById id, defer err, image
  if err then return [ err, null ]
  await check_permissions user, image, defer err, permitted
  return [ err, image ]

Если вы используете autocb, что не является моей любимой функцией ICS, то компилятор будет вызывать autocb каждый раз, когда вы возвращаетесь/замыкаетесь из функции. По опыту я считаю, что эта конструкция более подвержена ошибкам. Например, представьте, что вам нужно получить блокировку в начале функции, теперь вам нужно снять ее n раз. Другие могут не согласиться.

Еще одно замечание, указанное ниже в комментариях. autocb работает как return в том смысле, что принимает только одно значение. Если вы хотите вернуть несколько значений, как в этом примере, вам нужно вернуть массив или словарь. defer выполняет задания по деструктуризации, чтобы помочь вам:

await my_fn defer [err, image]
person Max Krohn    schedule 14.01.2013
comment
Спасибо, по какой-то причине параллельное выполнение такого рода последовательных команд сработало для меня, я исправлю это на своей стороне, и кажется, что «... если» - достаточно хорошее решение. Кроме того, что касается autocb, я раньше не слышал об этой функции, где она задокументирована? - person Andrew; 15.01.2013
comment
Спасибо, было бы хорошо иметь ссылку на этот документ где-нибудь в файле readme, потому что он все еще указывает на слегка устаревший страница maxtaco.github.com/coffee-script - person Andrew; 16.01.2013
comment
autocb не работает с обратными вызовами в стиле Node. Вы можете вернуть только одно значение. Приведенный выше код вызовет ошибку. См. github.com/maxtaco/coffee-script/issues/29. - person vaughan; 09.05.2013
comment
Вы правы, спасибо, что указали на это. Я исправил пример, вернув пары. - person Max Krohn; 11.05.2013

Как обсуждалось в проблеме № 35 репозитория IcedCoffeeScript, существует еще один метод, основанный на замороженном коннекторы в стиле -style, которые представляют собой функции, которые принимают на вход обратный вызов/отсрочку и возвращают другой обратный вызов/отсрочку.

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

Первым шагом является создание коннектора, который я называю «ErrorShortCircuiter» или «ESC»:

{make_esc} = require 'iced-error'

Что реализовано так:

make_esc = (gcb, desc) -> (lcb) ->
    (err, args...) ->
        if not err? then lcb args...
        else if not gcb.__esc
            gcb.__esc = true
            log.error "In #{desc}: #{err}"
            gcb err

Чтобы увидеть, что это делает, рассмотрим пример того, как это использовать:

my_fn = (gcb) ->
    esc = make_esc gcb, "my_fn"
    await socket.get 'image id', esc defer id
    await Image.findById id, esc defer image
    await check_permissions user, image, esc defer permitted
    gcb null, image

Эта версия my_fn сначала создает ErrorShortCircuiter (или esc), задача которого состоит из двух частей: (1) запускать gcb с объектом ошибки; и (2) записать сообщение о том, где произошла ошибка и что это была за ошибка. Очевидно, вы должны изменить точное поведение в зависимости от ваших настроек. Затем все последующие вызовы библиотечных функций с обратными вызовами будут получать обратные вызовы, сгенерированные defer, как обычно, а затем выполняться через коннектор esc, что изменит поведение обратного вызова. Новое поведение состоит в том, чтобы вызывать глобальную функцию gcb в случае ошибки и позволять текущему блоку await завершиться в случае успеха. Кроме того, в случае успеха нет необходимости иметь дело с нулевым объектом ошибки, поэтому заполняются только последующие слоты (например, id, image и permitted).

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

person Max Krohn    schedule 20.05.2013
comment
Смотрите мой комментарий к проблеме. Ваша ошибка должна быть объектом Error, а не ванильной строкой. - person Max Krohn; 12.01.2015