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

问题描述 投票:1回答:2

我有一个简单的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;
}

然后,我在这个简单的代码片段中使用此函数,以确保将获取请求(使用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调用的URL。在这种情况下,超时确实会触发,但抛出的TimeoutError不会被catch块捕获,而是终止正在运行的程序。

我有点困惑。我现在使用异步/等待很多,但我的理解可能仍然有一些粗糙的边缘。任何人都可以解释我如何有效地捕获此错误并处理它?

javascript error-handling async.js
2个回答
4
投票

抛出的错误只有在其直接封闭的函数具有某种错误处理时才会被捕获。传递给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);
   }
})();

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的所有“同步”样式)编写它。

特别是这个例子不能仅使用await编写,因为你需要同时运行两个任务(超时和请求)。你可以使用Promise.race(),但你仍然需要一个Promise来使用。

© www.soinside.com 2019 - 2024. All rights reserved.