Skip to content

实现 koa 中间件

koa 中间件具备以下特点

  • 每个中间件接受 next 函数,控制继续向下个中间件执行
  • next(error) 调用时,抛出异常,中断调用栈
  • await next() 前代码按中间件正序运行,后面代码按逆序运行(即洋葱模型)

例如,实现 run 函数,输出以下结果

ts
run(() => {
  console.log('run task');
  return Promise.resolve(1);
}, [
  (next) => {
    console.log('1 entry');
    await next();
    console.log('1 leave');
  },
  (next) => {
    console.log('2 entry');
    await next();
    console.log('2 leave');
  },
]);

// 1 entry
// 2 entry
// run task
// 2 leave
// 1 leave

使用 async await 实现

逆序遍历中间件列表,层层包裹 task 实现

ts
function run(task, middlewares: any[]) {
  const composeTask = middlewares.reverse().reduce((prev, curr) => {
    return async () => {
      await curr(prev);
    };
  }, task);

  return composeTask();
}

实现 next(error) 调用抛出异常

ts
function run(task, middlewares: any[]) {
  // 包裹 task 接收 error 参数抛出
  const composeTask = [...middlewares, task].reverse().reduce((prev, curr) => {
    return async (error) => {
      if (error) {
        throw error;
      }
      await curr(prev);
    };
    // 省略第二个参数,会从第二个元素开始遍历
  }, undefined);

  return composeTask();
}

如果其中一个中间件不使用 await 方式调用 next 会出现以下输出

ts
run(() => {
  console.log('run task');
  return Promise.resolve(1);
}, [
  (next) => {
    console.log('1 entry');
    next();
    console.log('1 leave');
  },
  (next) => {
    console.log('2 entry');
    await next();
    console.log('2 leave');
  },
]);

// 1 entry
// 2 entry
// run task
// 1 leave
// 2 leave

可以发现,entry 代码是按正确循序执行,但是 leave 代码,由于第二个中间件 await next() 后面代码添加成微任务,退出第一个中间件 next 调用,执行接下来语句,所以先输出 “1 leave”,再执行微任务,输出 “2 leave”