Должен ли асинхронный API когда-либо бросать синхронно?

Я пишу функцию JavaScript, которая делает запрос HTTP и возвращает обещание для результата (но этот вопрос в равной степени относится к реализации на основе обратного вызова).

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

Насколько важно, чтобы асинхронная функция всегда вела себя асинхронно, особенно для условий ошибки? Можно ли использовать throw, если вы знаете, что программа не находится в подходящем состоянии для продолжения операции async?

например:

function getUserById(userId, cb) {
  if (userId !== parseInt(userId)) {
    throw new Error('userId is not valid')
  }

  // make async call
}

// OR...

function getUserById(userId, cb) {
  if (userId !== parseInt(userId)) {
    return cb(new Error('userId is not valid'))
  }

  // make async call
}

Ответы

Ответ 1

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

Мое мнение о том, что ваш второй вариант - передача ошибки в обратный вызов - кажется более элегантным. В противном случае вы получите код, который выглядит так:

try {
    getUserById(7, function (response) {
       if (response.isSuccess) {
           //Success case
       } else {
           //Failure case
       }
    });
} catch (error) {
    //Other failure case
}

Управляющий поток здесь немного запутан.

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

Ответ 2

Насколько важно, чтобы асинхронная функция всегда вела себя асинхронно, особенно для условий ошибки?

Очень важный.

Можно ли использовать throw, если вы знаете, что программа не находится в подходящем состоянии для продолжения операции async?

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

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

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

И повторить @Timothy: вы всегда должны документировать поведение и поддерживать последовательность в поведении.

Ответ 3

API обратного вызова в идеале не должен бросать, но они бросают, потому что его очень трудно избежать, поскольку вы должны пытаться поймать буквально всюду. Помните, что ошибка бросания явно throw не требуется для функции, которую нужно выполнить. Еще одна вещь, которая добавляет к этому, заключается в том, что обратный вызов пользователя также может легко запускаться, например, вызывать JSON.parse без try catch.

Итак, вот как выглядит код, который ведет себя в соответствии с этими идеалами:

readFile("file.json", function(err, val) {
    if (err) {
        console.error("unable to read file");
    }
    else {
        try {
            val = JSON.parse(val);
            console.log(val.success);
        }
        catch(e) {
            console.error("invalid json in file");
        }
    }
});

Использование двух разных механизмов обработки ошибок действительно неудобно, поэтому, если вы не хотите, чтобы ваша программа была хрупким карточным домиком (не записывая ни одного попытки поймать когда-либо), вы должны использовать promises, которые объединяют все исключения обработка по единому механизму:

readFile("file.json").then(JSON.parse).then(function(val) {
    console.log(val.success);
})
.catch(SyntaxError, function(e) {
    console.error("invalid json in file");
})
.catch(function(e){
    console.error("unable to read file")
})