脚本怎样控制中间件执行顺序

wen 实用脚本 31

本文目录导读:

脚本怎样控制中间件执行顺序

  1. 核心原则:“Next” 函数
  2. Express.js 风格(顺序执行,基于回调)
  3. Koa.js 风格(洋葱模型,基于 async/await)
  4. 自定义 Pipeline(手写控制)
  5. 特殊场景:并行/条件性执行
  6. 总结:如何控制顺序?

通过函数的调用链和参数传递机制,显式或隐式地决定下一个中间件何时执行

不同的中间件架构(如 Express/Koa/自定义Pipeline)控制方式略有不同,但原理相通,下面从最常见的场景和底层原理来详细说明。


核心原则:“Next” 函数

几乎所有的中间件系统(如 Express、Koa、Redux、Webpack Loader)都使用一个 next 函数来控制流程。

  • 调用 next():表示“当前中间件处理完毕,将控制权交给下一个中间件”。
  • 不调用 next():表示“请求到此为止”,后续中间件不会执行。
  • 调用 next(error):表示“发生错误,跳转到错误处理中间件”。
  • 调用 next('route')(某些框架特有):表示“跳过当前路由的剩余中间件,直接匹配下一个路由”。

Express.js 风格(顺序执行,基于回调)

在 Express 中,中间件按它们在代码中出现的顺序被添加到请求处理链中。

const express = require('express');
const app = express();
// 中间件 1:日志记录(先执行)
app.use((req, res, next) => {
    console.log('1. 日志记录中间件');
    // 关键:必须调用 next(),否则卡住
    next(); 
});
// 中间件 2:请求解析(后执行)
app.use((req, res, next) => {
    console.log('2. 解析请求体');
    req.customData = { timestamp: Date.now() };
    next();
});
// 中间件 3:路由处理
app.get('/data', (req, res) => {
    console.log('3. 处理路由');
    res.send(`Timestamp: ${req.customData.timestamp}`);
});
// 错误处理中间件(特殊,4个参数)
app.use((err, req, res, next) => {
    console.error('错误中间件:', err.message);
    res.status(500).send('服务器错误');
});
app.listen(3000);

控制要点

  • 代码顺序决定优先级app.use(middleware1) 在前,middleware1 先执行。
  • next() 位置决定流程:可以在 next() 之前做前置处理(如日志),在 next() 之后做后置处理(如统一添加响应头)。

Koa.js 风格(洋葱模型,基于 async/await)

Koa 使用 async 函数和 await next() 实现“洋葱模型”,中间件执行顺序呈现先入后出的效果。

const Koa = require('koa');
const app = new Koa();
// 中间件 1:外层日志
app.use(async (ctx, next) => {
    console.log('1. 进入中间件1');
    await next(); // 进入下一个中间件,并挂起当前中间件
    console.log('5. 退出中间件1');
});
// 中间件 2:内层日志 + 耗时操作
app.use(async (ctx, next) => {
    console.log('2. 进入中间件2');
    await new Promise(resolve => setTimeout(resolve, 100)); // 模拟异步
    await next();
    console.log('4. 退出中间件2');
});
// 中间件 3:响应处理
app.use(async (ctx) => {
    console.log('3. 处理响应');
    ctx.body = 'Hello Koa';
});
app.listen(3000);
// 控制台输出:1 -> 2 -> (等待100ms) -> 3 -> 4 -> 5

控制要点

  • await next() 之前的代码是“上游”处理(请求到达时执行)。
  • await next() 之后的代码是“下游”处理(响应返回时执行)。
  • 利用这个模型,可以轻松实现请求计时、权限校验(前置)、响应格式统一(后置)等。

自定义 Pipeline(手写控制)

如果不依赖框架,可以通过一个数组 + 索引来手动控制顺序。

class MiddlewarePipeline {
    constructor() {
        this.middlewares = [];
    }
    use(fn) {
        this.middlewares.push(fn);
    }
    // 核心执行函数
    execute(req, res) {
        let index = 0;
        const next = () => {
            if (index < this.middlewares.length) {
                const middleware = this.middlewares[index++];
                // 将 req, res, next 传给中间件
                middleware(req, res, next);
            }
        };
        next(); // 启动链
    }
}
// 使用示例
const pipeline = new MiddlewarePipeline();
pipeline.use((req, res, next) => {
    console.log('A: 开始');
    req.data = {};
    next(); // 必须手动调用
    console.log('A: 结束');
});
pipeline.use((req, res, next) => {
    console.log('B: 处理中');
    req.data.time = Date.now();
    next();
});
pipeline.use((req, res, next) => {
    console.log('C: 最终处理');
    res.send(`Time: ${req.data.time}`);
});
// 模拟请求
pipeline.execute({ method: 'GET' }, { send: console.log });
// 输出:A: 开始 -> B: 处理中 -> C: 最终处理 -> A: 结束

控制要点

  • 通过维护一个递增的 index,确保中间件按注册顺序依次执行。
  • next() 函数内部调用下一个中间件,形成链式调用。

特殊场景:并行/条件性执行

有时需要更灵活的控制,跳过”或“并行”执行中间件。

1 条件跳过

// 只有特定条件下才执行中间件
app.use((req, res, next) => {
    if (req.query.bypass) {
        // 跳过当前中间件,直接进入下一个
        return next(); 
    }
    console.log('执行验证逻辑...');
    next();
});

2 并行中间件(手动控制)

在 Express 原生不支持并行中间件,但可以通过 Promise.all 手动实现:

app.use(async (req, res, next) => {
    const result = await Promise.all([
        fetch('http://service1'),
        fetch('http://service2')
    ]);
    req.service1Data = result[0];
    req.service2Data = result[1];
    next(); // 等两个请求都完成后,再进入下一个中间件
});

如何控制顺序?

控制方式 对应代码 效果
注册顺序 app.use(A); app.use(B); A 总是在 B 之前执行
next() 调用时机 next() 提前 vs 延后 决定上下游处理逻辑
条件判断 if(条件) return next() 跳过或选择中间件
错误传递 next(error) 跳转到错误处理链
异步等待 await next() (Koa) 挂起当前中间件,等内层执行完再继续

记住next 函数是控制流的总开关,只要理解了“谁调用了 next,谁就交出控制权”,就能精确控制任何中间件系统的执行顺序。

抱歉,评论功能暂时关闭!