93 # 实现 express 错误处理中间件

简介: 93 # 实现 express 错误处理中间件

上一节实现了 express 的中间件,这一节来实现错误处理中间件

执行某一步出错了,统一规定调用 next 传递的参数就是错误信息

先看 express 实现的demo

const express = require("express");
const app = express();
app.use("/", (req, res, next) => {
    console.log("中间件1");
    // next();
    next("中间件1出错了");
});
app.use("/", (req, res, next) => {
    console.log("中间件2");
    next();
    // next("中间件2出错了");
});
app.use("/", (req, res, next) => {
    console.log("中间件3");
    next();
    // next("中间件3出错了");
});
app.get(
    "/",
    (req, res, next) => {
        console.log("路由1");
        next();
    },
    (req, res, next) => {
        res.end("出错了 *****");
    }
);
app.listen(3000, () => {
    console.log(`server start 3000`);
    console.log(`在线访问地址:http://localhost:3000/`);
});

然后去访问:http://localhost:3000/

错误处理中间价,里面必须要有 4 个 参数(取函数的长度),放到栈的最底下

app.use((err, req, res, next) => {
    res.end(err);
})

下面实现处理逻辑

router/index.js

const url = require("url");
const Route = require("./route");
const Layer = require("./layer");
const methods = require("methods");
function Router() {
    // 维护所有的路由
    this.stack = [];
}
Router.prototype.route = function (path) {
    // 产生 route
    let route = new Route();
    // 产生 layer 让 layer 跟 route 进行关联
    let layer = new Layer(path, route.dispatch.bind(route));
    // 每个路由都具备一个 route 属性,稍后路径匹配到后会调用 route 中的每一层
    layer.route = route;
    // 把 layer 放到路由的栈中
    this.stack.push(layer);
    return route;
};
methods.forEach((method) => {
    Router.prototype[method] = function (path, handlers) {
        // 1.用户调用 method 时,需要保存成一个 layer 当道栈中
        // 2.产生一个 Route 实例和当前的 layer 创造关系
        // 3.要将 route 的 dispatch 方法存到 layer 上
        let route = this.route(path);
        // 让 route 记录用户传入的 handler 并且标记这个 handler 是什么方法
        route[method](handlers);
    };
});
Router.prototype.use = function (path, ...handlers) {
    // 默认第一个是路径,后面是一个个的方法,路径可以不传
    if (typeof path === "function") {
        handlers.unshift(path);
        path = "/";
    }
    // 如果是多个函数需要循环添加层
    for (let i = 0; i < handlers.length; i++) {
        let layer = new Layer(path, handlers[i]);
        // 中间件不需要 route 属性
        layer.route = undefined;
        this.stack.push(layer);
    }
};
Router.prototype.handle = function (req, res, out) {
    console.log("请求到了");
    // 需要取出路由系统中 Router 存放的 layer 依次执行
    const { pathname } = url.parse(req.url);
    let idx = 0;
    let next = (err) => {
        // 遍历完后没有找到就直接走出路由系统
        if (idx >= this.stack.length) return out();
        let layer = this.stack[idx++];
        if (err) {
            console.log("统一对中间件跟路由错误处理");
            // 找错误处理中间件
            if (!layer.route) {
                // 如果是中间件自己处理
                layer.handle_error(err, req, res, next);
            } else {
                // 路由则跳过,继续携带错误向下执行
                next(err);
            }
        } else {
            // 需要判断 layer 上的 path 和当前请求路由是否一致,一致就执行 dispatch 方法
            if (layer.match(pathname)) {
                // 中间件没有方法可以匹配,不能是错误处理中间件
                if (!layer.route) {
                    if (layer.handler.length !== 4) {
                        layer.handle_request(req, res, next);
                    } else {
                        next();
                    }
                } else {
                    // 将遍历路由系统中下一层的方法传入
                    // 加速匹配,如果用户注册过这个类型的方法在去执行
                    if (layer.route.methods[req.method.toLowerCase()]) {
                        layer.handle_request(req, res, next);
                    } else {
                        next();
                    }
                }
            } else {
                next();
            }
        }
    };
    next();
};
module.exports = Router;

layer.js

function Layer(path, handler) {
    this.path = path;
    this.handler = handler;
}
Layer.prototype.match = function (pathname) {
    if (this.path === pathname) {
        return true;
    }
    // 如果是中间件,进行中间件的匹配规则
    if (!this.route) {
        if (this.path == "/") {
            return true;
        }
        // /aaaa/b 需要 /aaaa/ 才能匹配上
        return pathname.startsWith(this.path + "/");
    }
    return false;
};
Layer.prototype.handle_error = function (err, req, res, next) {
    if (this.handler.length === 4) {
        // 调用错误处理中间件
        return this.handler(err, req, res, next);
    }
    next(err); // 普通的中间件
};
Layer.prototype.handle_request = function (req, res, next) {
    this.handler(req, res, next);
};
module.exports = Layer;

route.js

const Layer = require("./layer");
const methods = require("methods");
function Route() {
    this.stack = [];
    // 用来描述内部存过哪些方法
    this.methods = {};
}
Route.prototype.dispatch = function (req, res, out) {
    // 稍后调用此方法时,回去栈中拿出对应的 handler 依次执行
    let idx = 0;
    console.log("this.stack----->", this.stack);
    let next = (err) => {
        // 如果内部迭代的时候出现错误,直接到外层的 stack 中
        err && out(err);
        // 遍历完后没有找到就直接走出路由系统
        if (idx >= this.stack.length) return out();
        let layer = this.stack[idx++];
        console.log("dispatch----->", layer.method);
        if (layer.method === req.method.toLowerCase()) {
            layer.handle_request(req, res, next);
        } else {
            next();
        }
    };
    next();
};
methods.forEach((method) => {
    Route.prototype[method] = function (handlers) {
        console.log("handlers----->", handlers);
        handlers.forEach((handler) => {
            // 这里的路径没有意义
            let layer = new Layer("/", handler);
            layer.method = method;
            // 做个映射表
            this.methods[method] = true;
            this.stack.push(layer);
        });
    };
});
module.exports = Route;

测试demo

const express = require("./kaimo-express");
const app = express();
app.use("/", (req, res, next) => {
    console.log("中间件1");
    next();
    // next("中间件1出错了");
});
app.use("/", (req, res, next) => {
    console.log("中间件2");
    // next();
    next("中间件2出错了");
});
app.use("/", (req, res, next) => {
    console.log("中间件3");
    next();
    // next("中间件3出错了");
});
app.get(
    "/",
    (req, res, next) => {
        console.log("路由1");
        next();
    },
    (req, res, next) => {
        res.end("出错了 *****");
    }
);
// 错误处理中间价
app.use((err, req, res, next) => {
    console.log("错误处理中间价----->", err);
    res.end(err);
});
app.listen(3000, () => {
    console.log(`server start 3000`);
    console.log(`在线访问地址:http://localhost:3000/`);
});

目录
相关文章
|
26天前
|
Web App开发 JSON JavaScript
Node.js 中的中间件机制与 Express 应用
Node.js 中的中间件机制与 Express 应用
|
7月前
|
存储 缓存 JSON
玩转Express(二)登录态&中间件
玩转Express(二)登录态&中间件
|
6月前
|
JSON 中间件 API
中间件API示例(以Express.js为例)
【6月更文挑战第14天】
51 8
|
7月前
|
JavaScript 中间件 API
中间件应用Express.js(Node.js)
【5月更文挑战第3天】我们定义了一个名为 `logger` 的中间件函数。它接受请求对象、响应对象以及下一个中间件函数作为参数。当接收到请求时,它会打印出请求的 HTTP 方法和 URL,然后调用 `next()` 函数来将控制权传递给下一个中间件或路由处理器。我们使用 `app.use()` 方法将 `logger` 中间件添加到了应用级别的中间件堆栈中,这意味着它将对所有请求生效。
50 3
中间件应用Express.js(Node.js)
|
7月前
|
开发框架 JavaScript 中间件
深入探索Node.js的Express框架:使用与中间件详解
【4月更文挑战第30天】本文深入探讨了Node.js的Express框架,介绍了其作为Web开发的强大工具,主要聚焦于基本使用和中间件。Express是基于Node.js的Web应用框架,用于构建高效的应用和API。文章详细讲解了如何安装Express,创建简单应用,以及中间件的工作原理和应用,包括中间件的顺序、错误处理和挂载位置。此外,还提到了使用第三方中间件扩展功能。理解Express基础和中间件对于开发高质量Web应用至关重要。
|
7月前
|
Web App开发 JavaScript 前端开发
Express 框架的特点、使用方法以及相关的常用功能和中间件
Express 框架的特点、使用方法以及相关的常用功能和中间件
369 1
|
7月前
|
JavaScript 前端开发 中间件
Node.js—Express使用、Express 路由 、Express 中间件、托管静态资源、使用 Express 写接口、node.js链接sqlite数据库
Node.js—Express使用、Express 路由 、Express 中间件、托管静态资源、使用 Express 写接口、node.js链接sqlite数据库
245 0
|
7月前
|
消息中间件 存储 负载均衡
消息中间件的选择:RabbitMQ是一个明智的选择
消息中间件的选择:RabbitMQ是一个明智的选择
120 0
|
6月前
|
消息中间件 存储 中间件
【消息中间件】详解三大MQ:RabbitMQ、RocketMQ、Kafka
【消息中间件】详解三大MQ:RabbitMQ、RocketMQ、Kafka
1648 0
|
5月前
|
消息中间件 编解码 Docker
Docker部署RabbitMQ消息中间件
【7月更文挑战第4天】Docker部署RabbitMQ消息中间件
281 3