Попробуйте / поймать блоки с асинхронным / ожиданием
Я копаюсь в узле 7 async/await и продолжаю сталкиваться с таким кодом
async function main() {
try {
var quote = await getQuote();
console.log(quote);
} catch(error) {
console.error(error);
}
}
Кажется, это единственная возможность разрешить / отклонить или вернуть / выбросить с помощью async / await, однако v8 не оптимизирует код внутри блоков try / catch?!
Есть ли альтернативы?
10 ответов
альтернативы
Альтернатива этому:
async function main() {
try {
var quote = await getQuote();
console.log(quote);
} catch (error) {
console.error(error);
}
}
было бы что-то вроде этого, используя обещания явно:
function main() {
getQuote().then((quote) => {
console.log(quote);
}).catch((error) => {
console.error(error);
});
}
или что-то вроде этого, используя стиль передачи продолжения:
function main() {
getQuote((error, quote) => {
if (error) {
console.error(error);
} else {
console.log(quote);
}
});
}
Оригинальный пример
Ваш оригинальный код приостанавливает выполнение и ждет обещания, возвращенного getQuote()
урегулировать. Затем он продолжает выполнение и записывает возвращаемое значение в var quote
и затем печатает его, если обещание было разрешено, или выдает исключение и запускает блок catch, который печатает ошибку, если обещание было отклонено.
Вы можете сделать то же самое, используя Promise API, как во втором примере.
Спектакль
Теперь о производительности. Давайте проверим это!
Я только что написал этот код - f1()
дает 1
в качестве возвращаемого значения, f2()
бросает 1
как исключение:
function f1() {
return 1;
}
function f2() {
throw 1;
}
Теперь давайте вызовем один и тот же код миллион раз, сначала f1()
:
var sum = 0;
for (var i = 0; i < 1e6; i++) {
try {
sum += f1();
} catch (e) {
sum += e;
}
}
console.log(sum);
И тогда давайте изменим f1()
в f2()
:
var sum = 0;
for (var i = 0; i < 1e6; i++) {
try {
sum += f2();
} catch (e) {
sum += e;
}
}
console.log(sum);
Это результат, который я получил за f1
:
$ time node throw-test.js
1000000
real 0m0.073s
user 0m0.070s
sys 0m0.004s
Это то, что я получил за f2
:
$ time node throw-test.js
1000000
real 0m0.632s
user 0m0.629s
sys 0m0.004s
Кажется, что вы можете сделать что-то вроде 2 миллионов бросков в секунду в одном однопоточном процессе. Если вы делаете больше, то вам, возможно, придется беспокоиться об этом.
Резюме
Я бы не стал беспокоиться о таких вещах в Node. Если такие вещи будут часто использоваться, то в конечном итоге они будут оптимизированы командами V8 или SpiderMonkey или Chakra, и все будут следовать - это не так, как будто это не оптимизировано в принципе, это просто не проблема.
Даже если он не оптимизирован, я все равно буду утверждать, что если вы увеличиваете свой ЦП в Node, то вам, вероятно, следует написать сокращение числа в C - для этого, помимо всего прочего, и используются нативные дополнения. Или, может быть, такие вещи, как node.native, лучше подходят для работы, чем Node.js.
Мне интересно, какой будет вариант использования, который должен вызывать так много исключений. Обычно выдача исключения вместо возврата значения, ну, в общем, исключение.
Альтернатива, аналогичная обработке ошибок в Golang
Поскольку async / await использует скрытые обещания, вы можете написать небольшую служебную функцию, например:
export function catchEm(promise) {
return promise.then(data => [null, data])
.catch(err => [err]);
}
Затем импортируйте его всякий раз, когда вам нужно поймать некоторые ошибки, и оберните вашу асинхронную функцию, которая возвращает обещание с ним.
import catchEm from 'utility';
async performAsyncWork() {
const [err, data] = await catchEm(asyncFunction(arg1, arg2));
if (err) {
// handle errors
} else {
// use data
}
}
Альтернативой блоку try-catch является библиотека await-to-js. Я часто использую это. Например:
import to from 'await-to-js';
async function main(callback) {
const [err,quote] = await to(getQuote());
if(err || !quote) return callback(new Error('No Quote found');
callback(null,quote);
}
Этот синтаксис намного чище по сравнению с try-catch.
async function main() {
var getQuoteError
var quote = await getQuote().catch(err => { getQuoteError = err }
if (getQuoteError) return console.error(err)
console.log(quote)
}
В качестве альтернативы вместо объявления возможного var для хранения ошибки в верхней части вы можете сделать
if (quote instanceOf Error) ...
Хотя это не сработает, если выдается что-то вроде ошибки TypeError или Reference. Вы можете убедиться, что это обычная ошибка, хотя с
async function main() {
var quote = await getQuote().catch(err => {
console.error(err)
return new Error('Error getting quote')
})
if (quote instanceOf Error) return quote // get out of here or do whatever
console.log(quote)
}
Я предпочитаю оборачивать все в большой блок try-catch, где создается несколько обещаний, и может быть затруднительно обрабатывать ошибку именно для обещания, которое его создало. С альтернативой, являющейся множеством блоков try-catch, которые я считаю одинаково громоздкими
Более чистой альтернативой будет следующее:
Из-за того, что каждая асинхронная функция технически является обещанием
Вы можете добавлять уловки к функциям при их вызове с помощью await
async function a(){
let error;
// log the error on the parent
await b().catch((err)=>console.log('b.failed'))
// change an error variable
await c().catch((err)=>{error=true; console.log(err)})
// return whatever you want
return error ? d() : null;
}
a().catch(()=>console.log('main program failed'))
Нет необходимости в try catch, так как все ошибки обещаний обрабатываются, и у вас нет ошибок кода, вы можете пропустить это в родительском элементе!!
Допустим, вы работаете с mongodb, если есть ошибка, вы можете предпочесть обработать ее в вызывающей ее функции, а не создавать оболочки или использовать уловки try.
Я думаю, что простой и хорошо объясненный пример взят из Упрощение асинхронного программирования с помощью async и await в MDN DOCS.
В качестве примера они используют API Fetch , а затем 2 типа, один обычный, а другой гибридный , где асинхронный и обещанный смешаны вместе.
Простой пример
async function myFetch() { let response = await fetch('coffee.jpg'); // Added manually a validation and throws an error if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } let myBlob = await response.blob(); let objectURL = URL.createObjectURL(myBlob); let image = document.createElement('img'); image.src = objectURL; document.body.appendChild(image); } myFetch() .catch(e => { // Catches the errors... console.log('There has been a problem with your fetch operation: ' + e.message); });
Гибридный подход
Поскольку ключевое слово async превращает функцию в обещание , вы можете реорганизовать свой код, чтобы использовать гибридный подход обещаний и ожидания , перенеся вторую половину функции в новый блок, чтобы сделать его более гибким:
async function myFetch() { // Uses async
let response = await fetch('coffee.jpg');
// Added manually a validation and throws an error
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.blob();
}
myFetch().then((blob) => { // uses plain promise
let objectURL = URL.createObjectURL(blob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}).catch(e => console.log(e));
Добавление обработки ошибок
Обычная асинхронная функция myFetch() { try { let response = await fetch('coffee.jpg');
if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } let myBlob = await response.blob(); let objectURL = URL.createObjectURL(myBlob); let image = document.createElement('img'); image.src = objectURL; document.body.appendChild(image); } catch(e) { console.log(e); } } myFetch();
Гибрид (Лучший)
async function myFetch() { let response = await fetch('coffee.jpg'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.blob(); } myFetch().then((blob) => { let objectURL = URL.createObjectURL(blob); let image = document.createElement('img'); image.src = objectURL; document.body.appendChild(image); }) .catch((e) => // Not need a try catch. This will catch it all already! console.log(e) );
Лучшее решение
Лучшим решением, которое следует этому принципу, но добавляет больше ясности, является этот ответ -> StackOverflow: я считаю, что блоки try/catch с async/await .Здесь
function promiseHandle(promise) {
return promise
.then(data => [null, data])
.catch(err => [err]);
}
async function asyncFunc(param1, param2) {
const [err, data] = await promiseHandle(expensiveFunction(param1, param2));
// This just to show, that in this way we can control what is going on..
if (err || !data) {
if (err) return Promise.reject(`Error but not data..`);
return Promise.reject(`Error but not data..`);
}
return Promise.resolve(data);
}
Нет необходимости в библиотеке, такой как await-to-js, подойдет простая однострочная функция (также показанная в других ответах):
const to = promise => promise.then(res => [null, res]).catch(err => [err || true, null]);
Применение:
async function main()
{
var [err, quote] = await to(getQuote());
if(err)
{
console.log('warn: Could not get quote.');
}
else
{
console.log(quote);
}
}
Однако, если ошибка приводит к завершению функции или программы, например:
async function main()
{
var [err, quote] = await to(getQuote());
if(err) return console.error(err);
console.log(quote);
}
Тогда вы могли бы также просто позволить ошибке возвращаться из main() автоматически, что в любом случае является предполагаемой целью исключения:
async function main()
{
var quote = await getQuote();
console.log(quote);
}
main().catch(err => console.error('error in main():', err));
Выдача ошибки против возврата ошибки
Если ожидается, что вы столкнетесь с ошибкой, которая, как ожидается, произойдет, то использование
throw
или же
reject
это плохая практика. Вместо этого пусть
getQuote()
функция всегда разрешает использование любого из них:
-
resolve([err, result])
-
resolve(null)
-
resolve(new Error(...))
-
resolve({error: new Error(), result: null})
- и т.п.
Выброс ошибки (или эквивалент в асинхронном режиме: отклонение обещания) должен оставаться исключением. Поскольку исключение возникает только тогда, когда что-то идет не так, и не должно происходить при обычном использовании, оптимизация не является приоритетом. Таким образом, единственным последствием исключения может быть завершение функции, что является поведением по умолчанию, если оно все равно не перехвачено.
Если вы не имеете дело с плохо разработанными сторонними библиотеками или используете стороннюю библиотечную функцию для непреднамеренного использования, вам, вероятно, не следует использовать
to
-функция.
Я хотел бы сделать так:)
const sthError = () => Promise.reject('sth error');
const test = opts => {
return (async () => {
// do sth
await sthError();
return 'ok';
})().catch(err => {
console.error(err); // error will be catched there
});
};
test().then(ret => {
console.log(ret);
});
Это похоже на обработку ошибок с co
const test = opts => {
return co(function*() {
// do sth
yield sthError();
return 'ok';
}).catch(err => {
console.error(err);
});
};
catch
По моему опыту, поступать таким образом опасно. Будет обнаружена любая ошибка, возникшая во всем стеке, а не только ошибка этого обещания (что, вероятно, не то, что вам нужно).
Второй аргумент обещания - это уже обратный вызов отказа / отказа. Лучше и безопаснее использовать это вместо этого.
Вот однострочный текст, который я написал, чтобы справиться с этим:
function wait<R, E>(promise: Promise<R>): [R | null, E | null] {
return (promise.then((data: R) => [data, null], (err: E) => [null, err]) as any) as [R, E];
}
// Usage
const [currUser, currUserError] = await wait<GetCurrentUser_user, GetCurrentUser_errors>(
apiClient.getCurrentUser()
);
В случае с Express framework я обычно следую следующему методу. Мы можем создать функцию, которая разрешает обещание. Как
catchAsync
функция:
const catchAsync = (fn) => (req, res, next) =>{
Promise.resolve(fn(req, res, next)).catch((err) => next(err));
});
Эта функция может быть вызвана везде, где нам требуется try/catch. Она принимает функцию, которую мы вызываем, и разрешает или отклоняет ее в зависимости от выполняемого действия. Вот как мы можем это назвать
const sampleFunction = catchAsync(async (req, res) => {
const awaitedResponse = await getResponse();
res.send(awaitedResponse);
});