如何处理Express和Node.js应用程序中的错误

发布于2/25/2020 来自:「前端知否」微信公众号

使用Express创建API时,我们定义了路由及其处理程序。在理想情况下,API的使用者只会向我们定义的路由发出请求,并且路由将正常运行。但是,我们不会生活在理想的世界中:)。 Express知道这一点,并使我们API中的错误处理变得轻而易举。

在这篇文章中,我将解释如何处理Express中的错误。

该代码只有一个JavaScript文件index.js,其内容如下:

const express = require("express");
const app = express();
const port = 3000;

app.get("/", (req, res, next) => {
res.send("Welcome to main route!");
});

app.get("/about", (req, res, next) => {
res.send("This is the about route!");
});

app.listen(port, () => console.log(`App listening on port: ${port}`));

创建一个新文件夹,npm init -y,然后创建npm i --save express。在此文件夹中创建index.js并将代码粘贴到其中。

错误来源

Express应用程序中可能会发生两种基本错误。

一种错误是对没有定义路由处理程序的路径发出请求。例如,index.js定义了两条get路由(//about)。我正在使用get路由,以便我们可以轻松地在浏览器中测试路由。

请注意,路由定义了请求路径,并对该路径发出请求时调用了中间件函数:

app.HTTPMethod(path, middleware)
// HTTPMethod = get, post, put, delete …

错误的另一个来源是当路由处理程序或代码中的其他任何地方出现问题时。例如,如下更新`ndex.js`中的第一个路由:


app.get(‘/’, (req, res, next) => {
// 通过抛出错误来破坏应用程序,从而模仿错误!
throw new Error(‘Something went wrong’);
res.send(‘Welcome to main route!’)
})

重新启动服务器并访问localhost:3000,您将看到一个错误和一个堆栈跟踪信息。

通过路由排序处理路由错误

删除在index.js中引发错误的语句。启动服务器并在浏览器中访问localhost:3000,您应该看到以下消息:

Welcome to the main route!

访问localhost:3000/about:

This is the about route!

Express如何查找路由?

Express创建了一个可以称为路由表的地方,它将路由按照代码中定义的顺序放置。当请求进入Web服务器时,URI通过路由表运行,并且使用表中的第一个匹配项-即使存在多个匹配项。

如果找不到匹配项,则Express将显示错误。要查看实际效果,请访问localhost:3000/contact,浏览器将显示:

Cannot GET /contact

检查路由表后,Express发现/ contact不匹配,因此它以错误响应。

如何利用路由顺序

由于Express在路由表中找不到给定URI时显示错误消息,因此这意味着我们通过确保此路由是路由表中的最后一条来定义用于处理错误的路由。错误路由应匹配哪条路径?

由于我们不知道用户将请求的路径不存在,因此我们无法将路径硬编码到此错误路由中。我们也不知道请求可能使用哪种HTTP方法,因此我们将使用app.use()而不是app.get。

将以下路由放在app.listen()之前的路由声明的末尾,更新index.js:


// 这个匹配所有路由和所有请求方法
app.use((req, res, next) => {
res.status(404).send({
status: 404,
error: ‘Not found’
})
})

app.listen(port …

重新启动服务器并访问未定义的路径,例如localhost:3000/blog

现在,我们有了一个自定义的错误响应:

{ "status": 404, "error": "Not found" }

请记住,路由的顺序对于此工作非常重要。如果此错误处理路由位于路由声明的顶部,则每个路径(有效和无效)都将与其匹配。我们不希望这样,因此错误处理路由必须最后定义。

处理任何类型的错误

如果我们只想处理从请求到不存在路径的错误,则上一节中的解决方案有效。但是它不能处理我们的应用程序中可能发生的其他错误,并且是处理错误的不完整方法。它只能解决一半的问题。

更新index.js,在第一个get路由中引发错误:


app.get(‘/’, (req, res, next) => {
throw new Error(‘Something went wrong!’);
res.send(‘Welcome to main route!’)
})

如果您访问localhost:3000,您仍然会看到Express默认错误处理程序的响应。

定义错误处理中间件

错误处理中间件函数的声明方式与其他中间件函数相同,只是它们具有四个参数而不是三个参数。例如:

// 错误处理中间件
app.use((error, req, res, next) => {
console.error(error.stack);
res.status(500).send(‘Something Broke!’);
})

将此代码放在app.listen之前和第一个app.use之后,然后重新启动服务器,然后访问localhost:3000。现在的响应是:

Something Broke!

现在,我们正在处理两种类型的错误。啊哈!

这行得通,但是我们可以改善它吗?是的。

当您将参数传递给next()时,Express会假定这是一个错误,它将跳过所有其他路由,并将传递给next()的所有内容发送到已定义的错误处理中间件。

更新index.js:


app.use((req, res, next) => {
const error = new Error(“Not found”);
error.status = 404;
next(error);
});

// 错误处理中间件
app.use((error, req, res, next) => {
res.status(error.status || 500).send({
error: {
status: error.status || 500,
message: error.message || ‘Internal Server Error’,
},
});
});

现在,处理错误请求的中间件功能将移交给错误处理程序中间件。 next(error)表示:“嘿,错误处理程序先生,我有一个错误,请处理!”。

为了确保您与我在同一页面上,请输入error.status ||。 500表示如果错误对象没有status属性,我们将500用作状态代码。 index.js的完整内容是:

const express = require("express");

const app = express();
const port = 3000;

app.get("/", (req, res, next) => {
throw new Error("Something went wrong!");
res.send("Welcome to main route!");
});

app.get("/about", (req, res, next) => {
res.send("This is the about route!");
});

app.use((req, res, next) => {
const error = new Error("Not found");
error.status = 404;
next(error);
});

// 错误处理中间件
app.use((error, req, res, next) => {
res.status(error.status || 500).send({
error: {
status: error.status || 500,
message: error.message || 'Internal Server Error',
},
});
});

app.listen(port, () => console.log(`App listening on port: ${port}`));

如果您提供的是静态页面而不是发送JSON响应,则逻辑仍然相同。您只需要更改错误处理程序中发生的事情即可。例如:

app.use((error, req, res, next) => {
console.error(error); // 打印输出错误
res.render('errorPage') // 渲染错误页面给用户
});