блоки try / catch с async / await

Я копаюсь в функции async / await узла 7 и продолжаю натыкаться на такой код

function getQuote() {
  let quote = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
  return quote;
}

async function main() {
  try {
    var quote = await getQuote();
    console.log(quote);
  } catch (error) {
    console.error(error);
  }
}

main();

Кажется, это единственная возможность разрешить / отклонить или вернуть / выбросить с помощью async / await, однако v8 не оптимизирует код в блоках try / catch ?!

Есть ли альтернативы?


person Patrick    schedule 30.11.2016    source источник
comment
Что означает «бросить после неудачного ожидания»? Если это ошибки? Если не вернет ожидаемый результат? Вы можете перебросить блокировку захвата.   -  person DevDig    schedule 30.11.2016
comment
afaik v8 оптимизирует try / catch, оператор throw является медленным   -  person Tamas Hegedus    schedule 30.11.2016
comment
Я все еще не понимаю вопроса. Вы используете старую цепочку обещаний, но я не думаю, что это будет быстрее. Итак, вас беспокоит производительность try-catch? Тогда что делать с async await?   -  person Tamas Hegedus    schedule 30.11.2016
comment
Проверьте мой ответ Я попытался найти более чистый подход   -  person zardilior    schedule 13.06.2019
comment
Здесь вы можете сделать это. stackoverflow.com/a/61833084/6482248 Выглядит чище   -  person Prathamesh More    schedule 16.05.2020
comment
См. Также синтаксис для обработки отклонений с помощью _1 _ / _ 2_   -  person Bergi    schedule 15.06.2020


Ответы (7)


Альтернативы

Альтернатива этому:

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.

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

person rsp    schedule 30.11.2016
comment
Я знаю, что код можно легко написать с помощью Promises, как уже упоминалось, я видел это на различных примерах, поэтому я спрашиваю. Наличие одной операции в try / catch может не быть проблемой, но могут быть несколько функций async / await с дополнительной логикой приложения. - person Patrick; 02.12.2016
comment
@Patrick может быть и будет - это разница между предположением и фактическим тестированием. Я протестировал его для одного утверждения, потому что это то, что было в вашем вопросе, но вы можете легко преобразовать мои примеры для проверки нескольких операторов. Я также предоставил несколько других вариантов написания асинхронного кода, о которых вы тоже спрашивали. Если он отвечает на ваш вопрос, вы можете подумать о том, чтобы принять ответ . Подводя итог: конечно, исключения медленнее, чем возврат, но их использование должно быть исключением. - person rsp; 14.03.2017
comment
Исключение действительно должно быть исключением. При этом код не оптимизирован независимо от того, генерируете ли вы исключение или нет. Ухудшение производительности происходит из-за использования try catch, а не из-за генерации исключения. Хотя цифры невелики, согласно вашим тестам, он почти в 10 раз медленнее, что немаловажно. - person Nepoxx; 05.09.2017

Альтернатива, аналогичная обработке ошибок в 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
  }
}
person Steve Banton    schedule 16.03.2018
comment
Я создал пакет NPM, который делает именно то, что описано выше - npmjs.com/package/@simmo/task - person Mike; 22.08.2018
comment
@Mike Вы можете заново изобретать колесо - уже существует популярный пакет, который делает именно это: npmjs.com/package/await-to-js - person Jakub Kukul; 02.09.2018
comment
голанг не является узлом. - person Jeremiah Adams; 16.10.2020

Альтернативой блоку try-catch является await-to-js lib. Я часто им пользуюсь. Например:

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.

person Pulkit chadha    schedule 15.01.2018
comment
Пробовал и любил. Чистый и читаемый код за счет установки нового модуля. Но если вы планируете написать много асинхронных функций, я должен сказать, что это отличное дополнение! Спасибо - person filipbarak; 01.03.2018
comment
Вам даже не нужно устанавливать библиотеку. Если вы посмотрите на его исходный код, это буквально 1 функция. Просто скопируйте и вставьте эту функцию в служебный файл в своем проекте и готово. - person iqbal125; 21.12.2020

async function main() {
  var getQuoteError
  var quote = await getQuote().catch(err => { getQuoteError = err }

  if (getQuoteError) return console.error(err)

  console.log(quote)
}

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

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, которые я считаю одинаково громоздкими

person Tony    schedule 30.01.2018

Более чистой альтернативой будет следующее:

Из-за того, что каждая асинхронная функция технически является обещанием

Вы можете добавлять уловки к функциям при их вызове с помощью 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.

person zardilior    schedule 13.06.2019
comment
У вас есть 3 функции. Один получает значения и улавливает ошибку, другой вы возвращаете, если ошибки нет, и, наконец, вызов первой функции с обратным вызовом, чтобы проверить, вернула ли она ошибку. Все это решается одним блоком промис. Потом (cb) .catch (cb) или trycatch. - person Chief koshi; 20.05.2020
comment
@Chiefkoshi Как видите, ни одного улова не годится, поскольку во всех трех случаях ошибка обрабатывается по-разному. Если первый не работает, он возвращает d (), если второй терпит неудачу, он возвращает null, если последний терпит неудачу, отображается другое сообщение об ошибке. Вопрос касается обработки ошибок при использовании await. Так что это тоже ответ. Все должны выполняться, если какой-либо из них выйдет из строя. Для блоков try catch в этом конкретном примере потребуется три из них, что не чище. - person zardilior; 21.05.2020
comment
Вопрос не требует выполнения после невыполненных обещаний. Здесь вы ждете B, затем запускаете C и возвращаете D, если они ошиблись. Как этот чище? C должен ждать B, но они не зависят друг от друга. Я не вижу причины, по которой они были бы вместе, если бы они были независимы. Если бы они зависели друг от друга, вы бы хотели остановить выполнение C, если B не удается, задание .then.catch или try-catch. Я предполагаю, что они ничего не возвращают и выполняют некоторые асинхронные действия, совершенно не связанные с A. Почему они вызываются с помощью async await? - person Chief koshi; 22.05.2020
comment
Вопрос в том, есть ли альтернативы попыткам использования блоков catch для обработки ошибок при использовании async / await. Приведенный здесь пример носит описательный характер и является не чем иным, как примером. Он показывает последовательную индивидуальную обработку независимых операций, как обычно используется async / await. Почему они вызываются с помощью async await, просто чтобы показать, как с этим можно справиться. Его описательность более чем оправдана. - person zardilior; 23.05.2020

Я бы хотел так сделать :)

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);
  });
};
person Cooper Hsiung    schedule 12.03.2018
comment
Код не очень понятный, но выглядит интересно, не могли бы вы отредактировать? - person zardilior; 13.06.2019
comment
К сожалению, в этом ответе нет объяснения, потому что он действительно демонстрирует отличный способ избежать попытки перехвата каждой константы, которую вы назначаете с помощью await! - person Jim; 24.04.2020

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()
);
person sarink    schedule 26.06.2019