Должен ли асинхронный API когда-либо генерировать синхронно?
Я пишу функцию JavaScript, которая делает HTTP-запрос и возвращает обещание результата (но этот вопрос в равной степени относится к реализации на основе обратного вызова).
Если я сразу знаю, что аргументы, предоставленные для функции, являются недействительными, должна ли функция throw
синхронно, или он должен вернуть отклоненное обещание (или, если вы предпочитаете, вызвать обратный вызов с Error
пример)?
Насколько важно, чтобы асинхронная функция всегда работала асинхронно, особенно в случае ошибок? Это нормально для throw
если вы знаете, что программа не находится в подходящем состоянии для выполнения асинхронной операции?
например:
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
}
5 ответов
В конечном итоге решение о том, чтобы выкинуть синхронно или нет, остается за вами, и вы, скорее всего, найдете людей, которые спорят по обе стороны. Важно документировать поведение и поддерживать последовательность в поведении.
Мое мнение по этому вопросу таково, что ваш второй вариант - передача ошибки в обратный вызов - кажется более элегантным. В противном случае вы получите код, который выглядит следующим образом:
try {
getUserById(7, function (response) {
if (response.isSuccess) {
//Success case
} else {
//Failure case
}
});
} catch (error) {
//Other failure case
}
Поток управления здесь немного сбивает с толку.
Кажется, что было бы лучше иметь один if / else if / else
структура в обратном вызове и отказаться от окружающих try / catch
,
Это во многом вопрос мнения. Что бы вы ни делали, делайте это последовательно и четко документируйте.
Одна объективная информация, которую я могу дать вам, заключается в том, что это было предметом многочисленных дискуссий при разработке функций JavaScript, которые, как вы, возможно, знаете, неявно возвращают обещания для своей работы. Вы также можете знать, что часть функции, предшествующая первой или
В конце концов TC39 решил, что даже ошибки, возникающие в синхронной части функции, должны отклонять ее обещание, а не вызывать синхронную ошибку. Например:
Но после обсуждения они решили последовательно отказываться от обещаний.
Так что это одна конкретная часть информации, которую следует учитывать при принятии решения о том, как вы должны обращаться с этим в вашей собственной не-
Насколько важно, чтобы асинхронная функция всегда работала асинхронно, особенно в случае ошибок?
Это нормально для
throw
если вы знаете, что программа не находится в подходящем состоянии для выполнения асинхронной операции?
Да, я лично думаю, что все в порядке, когда эта ошибка очень отличается от любых асинхронно генерируемых ошибок, и ее нужно все равно обрабатывать отдельно.
Если известно, что некоторые идентификаторы пользователя являются недействительными, поскольку они не являются числовыми, а некоторые из них будут отклонены на сервере (например, потому что они уже получены), вы должны последовательно выполнять (асинхронный!) Обратный вызов для обоих случаев. Если асинхронные ошибки возникают только из-за проблем с сетью и т. Д., Вы можете сообщить о них по-другому.
Вы всегда можете throw
когда возникает "неожиданная" ошибка. Если вам требуются действительные идентификаторы пользователей, вы можете использовать недействительные. Если вы хотите предвидеть недопустимые из них и ожидать, что вызывающий их обработает, вы должны использовать "унифицированный" маршрут ошибки, который будет обещанием обратного вызова / отклонения для асинхронной функции.
И повторить @Timothy: вы всегда должны документировать поведение и поддерживать последовательность в поведении.
API-интерфейсы обратного вызова в идеале не должны генерировать, но они генерируют, потому что их очень трудно избежать, поскольку вы должны пытаться ловить буквально везде. Помните, что выбрасывая ошибку явно throw
не требуется для функции, чтобы бросить. Еще одна вещь, которая добавляет к этому, - то, что пользовательский обратный вызов может также легко бросить, например, вызов JSON.parse
без попытки поймать.
Вот как будет выглядеть код, который ведет себя в соответствии с этими идеалами:
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");
}
}
});
Необходимость использования 2-х различных механизмов обработки ошибок действительно неудобна, поэтому, если вы не хотите, чтобы ваша программа была хрупкой карточной карточкой (не записывая никакой попытки перехвата), вы должны использовать обещания, которые объединяют всю обработку исключений в рамках одного механизма.:
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")
})
В идеале у вас должна быть многоуровневая архитектура, такая как контроллеры, службы и т. д. Если вы выполняете проверки в службах, немедленно бросайте вызов и используйте блок catch в своем контроллере, чтобы поймать ошибку, отформатировать ее и отправить соответствующий код ошибки http. Таким образом, вы можете централизовать всю логику обработки плохих запросов. Если вы будете обрабатывать каждый случай, в конечном итоге вы напишете больше кода. Но именно так я бы поступил. Зависит от вашего варианта использования