Try/catch блоки с async/wait

Я копаюсь в функции асинхронного ожидания/ожидания узла 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();

Ответ 1

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

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

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

будет что-то вроде этого, явно используя promises:

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, который печатает ошибку, если обещание было отклонено.

Вы можете сделать то же самое, используя API Promise, как и во втором примере.

Производительность

Теперь для производительности. Пусть проверит его!

Я просто написал этот код - f1() дает 1 как возвращаемое значение, f2() throws 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, тогда вы, вероятно, должны написать свой номер crunching в C - то, для чего используются собственные дополнения, среди других вещи. Или, может быть, такие вещи, как node.native, лучше подходят для задания, чем Node.js.

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

Ответ 2

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

Ответ 3

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

Ответ 4

Альтернатива, аналогичная обработке ошибок в 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
  }
}

Ответ 5

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

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

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

Ответ 6

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

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);
  });
};

Ответ 7

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()
);