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

118

Я копаюсь в функции 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 ?!

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

Патрик
источник
Что означает «бросить после неудачного ожидания»? Если это ошибки? Если не вернет ожидаемый результат? Вы можете перебросить блокировку.
DevDig
afaik v8 действительно оптимизирует try / catch, инструкция throw является медленной
Тамас Хегедус
1
Я все еще не понимаю вопроса. Вы используете старую цепочку обещаний, но я не думаю, что это будет быстрее. Значит, вас беспокоит эффективность try-catch? Тогда что делать с async await?
Tamas Hegedus
Проверьте мой ответ Я попытался найти более чистый подход
zardilior
Здесь вы можете сделать это stackoverflow.com/a/61833084/6482248 Выглядит чище
Prathamesh More

Ответы:

135

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

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

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.

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

RSP
источник
Я знаю, что код можно легко написать с помощью обещаний, как уже упоминалось, я видел это на различных примерах, поэтому я спрашиваю. Наличие одной операции в try / catch может не быть проблемой, но могут быть несколько функций async / await с дополнительной логикой приложения.
Патрик
4
@Patrick «может быть» и «будет» - это разница между предположением и фактическим тестированием. Я тестировал его для одного утверждения, потому что это то, что было в вашем вопросе, но вы можете легко преобразовать мои примеры для проверки нескольких операторов. Я также предоставил несколько других вариантов написания асинхронного кода, о которых вы также спрашивали. Если он отвечает на ваш вопрос, вы можете принять ответ . Подводя итог: конечно, исключения медленнее, чем возврат, но их использование должно быть исключением.
RSP
1
Исключение действительно должно быть исключением. При этом код не оптимизирован независимо от того, генерируете ли вы исключение или нет. Ухудшение производительности происходит от использования try catch, а не от выброса исключения. Хотя цифры небольшие, согласно вашим тестам, он почти в 10 раз медленнее, что немаловажно.
Nepoxx 05
22

Альтернатива, аналогичная обработке ошибок в 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
  }
}
Стив Бэнтон
источник
Я создал пакет NPM, который делает именно то, что описано выше - npmjs.com/package/@simmo/task
Майк,
2
@Mike Вы, возможно, изобретаете колесо заново - уже существует популярный пакет, который делает именно это: npmjs.com/package/await-to-js
Якуб Кукул
21

Альтернативой блоку 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.

Pulkit chadha
источник
Пробовал и любил. Чистый и читаемый код за счет установки нового модуля. Но если вы планируете написать много асинхронных функций, я должен сказать, что это отличное дополнение! Спасибо
filipbarak 01
15
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, которые я считаю одинаково громоздкими

Тони
источник
8

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

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

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

zardilior
источник
У вас есть 3 функции. Один получает значения и перехватывает ошибку, другой вы возвращаете, если ошибки нет, и, наконец, вызов первой функции с обратным вызовом, чтобы проверить, вернула ли она ошибку. Все это решается одним блоком "промис" .then (cb) .catch (cb) или trycatch.
Главный коши
@Chiefkoshi Как видите, ни одного улова не годится, поскольку во всех трех случаях ошибка обрабатывается по-разному. Если первый не работает, он возвращает d (), если второй терпит неудачу, он возвращает null, если последний терпит неудачу, отображается другое сообщение об ошибке. Вопрос касается обработки ошибок при использовании await. Так что это тоже ответ. Все должны выполняться, если один из них не работает. Блоки
try
1
Вопрос не требует выполнения после невыполненных обещаний. Здесь вы ждете B, затем запускаете C и возвращаете D, если они ошиблись. Как этот уборщик? C должен ждать B, но они не зависят друг от друга. Я не вижу причины, по которой они были бы вместе, если бы они были независимы. Если бы они зависели друг от друга, вы бы хотели остановить выполнение C, если B не удается, задание .then.catch или try-catch. Я предполагаю, что они ничего не возвращают и выполняют некоторые асинхронные действия, совершенно не связанные с A. Почему они вызываются с помощью async await?
Главный коши
Вопрос в том, как можно использовать блоки catch для обработки ошибок при использовании async / await. Приведенный здесь пример носит описательный характер и представляет собой не что иное, как пример. Он показывает последовательную индивидуальную обработку независимых операций, как обычно используется async / await. Почему они вызываются с помощью async await, просто чтобы показать, как с этим можно справиться. Его описательность более чем оправдана.
зардилиор
2

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

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);
  });
};
Купер Сюн
источник
Код не очень понятный, но выглядит интересно, не могли бы вы отредактировать?
zardilior
К сожалению, в этом ответе нет объяснения, потому что он действительно демонстрирует отличный способ избежать попытки перехвата каждой константы, которую вы назначаете await!
Джим
0

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()
);
sarink
источник