1.8 全局异常处理

2023年 8月 15日 60.1k 0

上节最后提到:

思考下,目前代码层面还有什么致命缺陷?

我们先梳理下目前接口请求后代码流动情况:
可以看到,从mod.ts开始到最终model.ts处理完业务,中间每个环节都有可能产生异常,抛开oak框架本身处理的环节(1、2、3、8、9、10),仍有4、5、6、7随便哪个阶段产生错误,我们程序都有可能崩溃。

比如我们在user.service.ts中修改getAll,抛出一句错误:

async getAll(): Promise {
    throw new Error("getAll error"); // 新增这句
    return this.userModel.findAll();
}

再在浏览器请求http://localhost:8000/user,你看到了什么?

image.png

打开网络,也能看到接口响应变成了500。

image.png

这并不可怕,可怕的是回到vscode的控制台,看你的服务一大段报错信息:

image.png

这是未捕获的应用错误。好在框架帮你有个默认处理,服务并没有停止。

全局中间件异常捕获

针对这种情况,我们可以选择在上层控制层(user.router.ts)中捕获错误,保障代码调用的正确。但如果控制层的代码也出错了怎么办?

所以,我们还需要有个全局的异常捕获,修改main.ts,在const app = new Application()之后添加:

app.use(async (context, next) => {
  try {
    await next();
  } catch (error) {
    context.response.status = error.status || 500;
    context.response.body = error.message;
  }
});

必须是第一个中间件,思考下为什么。提示:洋葱模型。

这时,再在浏览器里访问一次http://localhost:8000/user:
image.png

全局错误捕获

我们加了全局的中间件异常捕获,是不是万事大吉,可以高枕无忧了?

当然不是。

src=http___img.tuguaishou.com_ips_templ_preview_w216_q100_16_3c_ff_lg_3109643_1590558526_5ecdff3e06804.jpg_auth_key=2223302400-0-0-e926fc20dd289eff54a7b17b50f9fa46&refer=http___img.tuguaishou.webp

再次修改user.service.ts的getAll,延迟抛出一个错误:

async getAll(): Promise {
    setTimeout(() => {
      throw new Error("getAll error");
    }, 0);
    return this.userModel.findAll();
}

继续在浏览器请求http://localhost:8000/user。

咦,你看到接口是正常的:

image.png

不要高兴太早,再刷新一下,是不是看到浏览器在不断转圈了?

image.png

赶快看你的vscode的控制台:

image.png

这次服务是真挂了!只能重启服务了。

这是怎么回事呢?

再仔细看

try {
  await next();
} catch (error) {
...
}

原来我们这段代码try/catch只是捕获next函数这个Promise结束,可以说是中间件范围内的Promise异常捕获,而setTimeout跳出了这个控制,是无法被它捕获的。

那这种情况应该怎么办呢?

Deno实现了浏览器的绝大多数API,windows.onerror、addEventListener也不例外,所以可以再加个全局监听,当然,也不能忘了阻止默认处理。

addEventListener("error", (evt) => {
  evt.preventDefault(); // 这句很重要
  console.error(`global`, evt.error);
});

注意:这只是一个兜底方案,正常情况下,不允许在正常代码中使用setTimeout等定时器跳出当前业务逻辑。如果是想要延时,也需要将它包装成Promise,再用await等待结果返回,又或者主动处理好错误。

全局错误捕获不了的情况

经过上边两步,你可能以为已经拥有了不灭金身:

事实打脸会很快。

开发者容易犯的一个错误是,上层调用的下层方法通常是一个Promise,但忘了加await等待它结束。

比如这样,我们再修改getAll方法:

 async getAll(): Promise {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject(new Error("error"));
      }, 100);
    });
  }

再把上层user.router.ts调用的await去掉:

image.png

刷新http://localhost:8000/user,这次不用看页面结果,直接看vscode控制台:

image.png

不出意外,服务又挂了!

在浏览器中,window.onerroraddEventListener监听error事件,只能捕获普通错误,对未处理的Promise是无解的,只能用另一个事件unhandledrejection

addEventListener("unhandledrejection", (evt) => {
  evt.preventDefault();
  console.error(`unhandledrejection`, evt.reason);
});

Deno直到v1.24才实现这个事件。而Node.js则有同样的事件:
process.on('unhandledRejection', (reason, promise) => {});

尽管如此,我们还是应该在平时养成良好的习惯,对使用的Promise进行异常捕获,如使用catch,又或者await。

作业

有了上面3点,已经能尽可能保障服务不宕机了(并非百分百)。但这只是一个服务最基本的需求。再想想看,我们的接口完美无缺了吗?提醒下,是不是还没有进行参数校验?

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论