try / catch块未捕获异步/等待错误

时间:2018-09-06 05:41:14

标签: javascript error-handling async.js

我有一个简单的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,但据我了解,我可能仍会遇到一些困难。谁能解释我如何有效地捕获并处理此错误?

3 个答案:

答案 0 :(得分:4)

仅当其直接封闭函数具有某种错误处理功能时,才会捕获引发的错误。传递给setTimeout的匿名函数本身不是async函数,因此如果 separate 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在传递{{1}之后会被拒绝}参数:

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

答案 1 :(得分:1)

此错误在单独的调用堆栈中发生,因为它是从回调内抛出的。它与try / catch块中的同步执行流完全分开。

您要在超时或成功回调中操作相同的Promise对象。这样的事情应该会更好:

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

在没有rejectedclearTimeout的情况下也可以正常工作,但是通过这种方式,您可以确保同时调用resolvereject,而不是同时调用。

您会发现我在这里的任何地方都没有使用awaitthrow!如果您在使用异步代码时遇到麻烦,最好先使用单一样式(使用await使用所有回调或所有Promise,或使用所有“同步”样式)来编写它。

尤其是,不能仅使用await编写此示例,因为您需要同时运行两个任务(超时和请求)。您可能使用Promise.race(),但仍需要Promise才能使用。

答案 2 :(得分:-1)

我将提供一些一般规则,因为已经给出了答案。

默认情况下,尝试/捕获是同步的。这意味着如果 异步函数在同步try / catch中引发错误 块,没有错误抛出。