繁体   English   中英

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

[英]try/catch block not catching async/await error

我有一个简单的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调用的负载。 在这些情况下,超时实际上会触发,但抛出的TimeoutError没有被 catch 块捕获,而是终止了正在运行的程序。

我有点困惑。 我现在确实经常使用 async/await,但在我的理解中我可能仍然有一些粗糙的地方。 谁能解释我如何有效地捕获此错误并处理它?

抛出的错误只有在其直接封闭的函数具有某种错误处理时才会被捕获。 传递给setTimeout匿名函数不是async函数本身,因此如果单独的timeout在一段时间后抛出, async函数不会停止执行:

 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传递给它,并在传递的ms参数之后传递一个拒绝的Promise

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

此错误发生在单独的调用堆栈中,因为它是从回调中抛出的。 它与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 ) );
        }
    } );
} );

如果没有rejectedclearTimeout ,它可能也能正常工作,但是这样您可以确保调用resolvereject ,而不是两者。

你会注意到我在这里没有使用awaitthrow 如果您在使用异步代码时遇到问题,最好先使用单一样式编写它(所有回调或所有承诺,或所有使用await “同步”样式)。

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

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

Try/catch 默认是同步的。 这意味着如果异步函数在同步 try/catch 块中抛出错误,则不会抛出错误。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM