блок try / catch не перехватывает ошибку async / await - PullRequest
0 голосов
/ 06 сентября 2018

У меня есть простая функция timeout, которая оборачивает асинхронную функцию тайм-аутом, чтобы гарантировать, что она выйдет из строя через заданный промежуток времени. Функция тайм-аута выглядит следующим образом:

export default async function<T = any>(
  fn: Promise<T>,
  ms: number,
  identifier: string = ""
): Promise<T> {
  let completed = false;
  const timer = setTimeout(() => {
    if (completed === false) {
      const e = new Error(`Timed out after ${ms}ms [ ${identifier} ]`);
      e.name = "TimeoutError";
      throw e;
    }
  }, ms);
  const results = await fn;
  completed = true;
timer.unref();

  return results;
}

Затем я использую эту функцию в этом простом фрагменте кода, чтобы запрос fetch (с использованием реализации node-fetch) был преобразован в текстовый вывод:

let htmlContent: string;
  try {
    htmlContent = await timeout<string>(
      response.text(),
      3000,
      `waiting for text conversion of network payload`
    );
  } catch (e) {
    console.log(
      chalk.grey(`- Network response couldn\'t be converted to text: ${e.message}`)
    );
    problemsCaching.push(business);
    return business;
  }

При выполнении этого кода на протяжении многих итераций большинство конечных точек URL-адресов предоставляют полезную нагрузку, которая может быть легко преобразована в текст, но иногда возникает одна ошибка, которая, кажется, просто вешает вызов fetch . В этом случае тайм-аут фактически срабатывает, но выброшенный TimeoutError НЕ перехватывается блоком catch, а завершает работающую программу.

Я немного сбит с толку. Я часто использую async / await, но у меня все еще могут быть некоторые грубые края в моем понимании. Может кто-нибудь объяснить, как я могу эффективно зафиксировать эту ошибку и обработать ее?

Ответы [ 2 ]

0 голосов
/ 06 сентября 2018

Эта ошибка происходит в отдельном стеке вызовов, потому что она вызывается из-за обратного вызова. Он полностью отделен от потока синхронного выполнения внутри блока try / catch.

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

return new Promise( ( resolve, reject ) => {
    let rejected = false;
    const timer = setTimeout( () => {
        rejected = true;
        reject( new Error( 'Timed out' ) );
    }, ms ).unref();
    fn.then( result => {
        clearTimeout( timer );
        if ( ! rejected ) {
            resolve( result ) );
        }
    } );
} );

Скорее всего, все будет прекрасно работать и без rejected и clearTimeout, но таким образом вы гарантируете, что вызывается либо resolve, либо reject, но не оба.

Вы заметите, что я нигде не использовал await или throw! Если у вас возникли проблемы с асинхронным кодом, лучше сначала написать его, используя один стиль (все обратные вызовы, или все обещания, или весь «синхронный» стиль, используя await).

Этот пример, в частности , не может быть записан с использованием только await, потому что вам нужно одновременно запустить две задачи (время ожидания и запрос). Возможно, вы могли бы использовать Promise.race(), но вам все еще нужно Promise для работы.

0 голосов
/ 06 сентября 2018

Сгенерированная ошибка будет обнаружена только в том случае, если ее непосредственно включающая в себя функция имеет своего рода обработку ошибок.Ваша анонимная функция, переданная в setTimeout, не является самой функцией async, поэтому функция async не прекратит выполнение, если через некоторое время выдает Отдельный timeout:

const makeProm = () => new Promise(res => setTimeout(res, 200));
(async () => {
  setTimeout(() => {
    throw new Error();
  }, 200);
  await makeProm();
  console.log('done');
})()
  .catch((e) => {
    console.log('caught');
  });

Это выглядит как хорошее время для использования Promise.race: передайте ему fetch Promise, а также передайте ему Promise, который отклоняется после пройденногоms параметр:

async function timeout(prom, ms) {
  return Promise.race([
    prom,
    new Promise((res, rej) => setTimeout(() => rej('timeout!'), ms))
  ])
}

(async () => {
  try {
    await timeout(
      new Promise(res => setTimeout(res, 2000)),
      500
    )
   } catch(e) {
      console.log('err ' + e);
   }
})();
...