• 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/`);
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

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

    在这里插入图片描述

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

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

    在这里插入图片描述

    下面实现处理逻辑

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    测试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/`);
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    在这里插入图片描述

  • 相关阅读:
    虹科分享 | 2023温控生物技术和医药物流前景展望专题报告
    springboot+vue+mysql+easyexcel实现文件导出+导出的excel单元格添加下拉列表
    【C语言刷LeetCode】451. 根据字符出现频率排序(M)
    XML对代码中的空白处理
    VUE响应式
    聊聊身边的嵌入式:点菜机用着好好的,突然挂了,这口锅应该甩给谁?
    力扣刷题 day14:10-14
    预付费系统在学生公寓用电中的发展前景-Susie 周
    园子周边:Polo 衫效果图预览
    LeetCode17电话号码的字母组合
  • 原文地址:https://blog.csdn.net/kaimo313/article/details/133259085