编码母题 / P0 / Node 与 JavaScript / 01
洋葱模型中间件组合器
先读清题意与边界,独立尝试,再展开思路并对照可运行的参考代码。
重要程度必练难度中等预计完成30–40 分钟
题目是什么
实现洋葱式中间件 compose。每层可在 await next() 前后执行逻辑;异常向外传播,同一层不得重复调用 next。
输入输出示例
A 前、B 前、B 后、A 后是两个中间件的典型执行顺序。
练习起手式与验证
复制代码后补全空方法,再运行下方三组验证;复杂题所需的辅助类型与输入准备一并放在代码块中。
/*
* 题目:洋葱模型中间件组合器
* 要求:实现洋葱式中间件 compose。每层可在 await next() 前后执行逻辑;
* 异常向外传播,同一层不得重复调用 next。
* 示例:A 前、B 前、B 后、A 后是两个中间件的典型执行顺序。
*/
function compose(middleware) {}
(async () => {
const order = [];
const fn = compose([
async (_, next) => {
order.push(1);
await next();
order.push(6);
},
async (_, next) => {
order.push(2);
await next();
order.push(5);
},
async () => {
order.push(3);
await Promise.resolve();
order.push(4);
},
]);
await fn({});
console.log(order); // 预期:[1, 2, 3, 4, 5, 6]
console.log(await Promise.resolve(compose([
async (_, next) => {
await next();
await next();
},
])({})).then(() => false, () => true)); // 预期:true(拒绝)
console.log(await Promise.resolve(compose([
() => {
throw new Error("boom");
},
])({})).then(() => false, () => true)); // 预期:true(拒绝)
})().catch(console.error);
/*
* 题目:洋葱模型中间件组合器
* 要求:实现洋葱式中间件 compose。每层可在 await next() 前后执行逻辑;
* 异常向外传播,同一层不得重复调用 next。
* 示例:A 前、B 前、B 后、A 后是两个中间件的典型执行顺序。
*/
function compose(middleware) {}
(async () => {
const order = [];
const fn = compose([
async (_, next) => {
order.push(1);
await next();
order.push(6);
},
async (_, next) => {
order.push(2);
await next();
order.push(5);
},
async () => {
order.push(3);
await Promise.resolve();
order.push(4);
},
]);
await fn({});
console.log(order); // 预期:[1, 2, 3, 4, 5, 6]
console.log(await Promise.resolve(compose([
async (_, next) => {
await next();
await next();
},
])({})).then(() => false, () => true)); // 预期:true(拒绝)
console.log(await Promise.resolve(compose([
() => {
throw new Error("boom");
},
])({})).then(() => false, () => true)); // 预期:true(拒绝)
})().catch(console.error);解题思路
展开思路、不变量与常见边界
关键切入点:dispatch(index) 返回 Promise 并推进调用链;索引只增不退,重复 next 必须拒绝。
核心模型:dispatch(index) 递归返回 Promise。index 只能严格递增;若当前 index 不大于已执行索引,说明 next 被重复调用。时间 O(n),异步调用链空间 O(n)。 常见错误:不 return/await next 导致外层提前结束;漏包 Promise.resolve;允许 next 多次推进同一链。迁移:Koa、拦截器、Agent Hook、Tool 中间件。
参考代码与验证
“关键机制”注释用于解释实现,下载链接提供未经页面注释修改的原始 JS 文件。
// 关键机制:dispatch(index) 返回 Promise 并推进调用链;索引只增不退,重复 next 必须拒绝。
function compose(middleware) {
if (
!Array.isArray(middleware) ||
!middleware.every((fn) => typeof fn === "function")
)
throw new TypeError("middleware must be functions");
return function execute(context, next) {
let lastIndex = -1;
function dispatch(index) {
if (index <= lastIndex)
return Promise.reject(new Error("next() called multiple times"));
lastIndex = index;
const fn = index === middleware.length ? next : middleware[index];
if (!fn) return Promise.resolve();
try {
return Promise.resolve(fn(context, () => dispatch(index + 1)));
} catch (error) {
return Promise.reject(error);
}
}
return dispatch(0);
};
}
// 三组以上可直接运行的验证;异步题使用独立执行块。
(async () => {
const order = [];
const fn = compose([
async (_, next) => {
order.push(1);
await next();
order.push(6);
},
async (_, next) => {
order.push(2);
await next();
order.push(5);
},
async () => {
order.push(3);
await Promise.resolve();
order.push(4);
},
]);
await fn({});
console.log(order); // 预期:[1, 2, 3, 4, 5, 6]
console.log(await Promise.resolve(compose([
async (_, next) => {
await next();
await next();
},
])({})).then(() => false, () => true)); // 预期:true(拒绝)
console.log(await Promise.resolve(compose([
() => {
throw new Error("boom");
},
])({})).then(() => false, () => true)); // 预期:true(拒绝)
})().catch(console.error);
// 关键机制:dispatch(index) 返回 Promise 并推进调用链;索引只增不退,重复 next 必须拒绝。
function compose(middleware) {
if (
!Array.isArray(middleware) ||
!middleware.every((fn) => typeof fn === "function")
)
throw new TypeError("middleware must be functions");
return function execute(context, next) {
let lastIndex = -1;
function dispatch(index) {
if (index <= lastIndex)
return Promise.reject(new Error("next() called multiple times"));
lastIndex = index;
const fn = index === middleware.length ? next : middleware[index];
if (!fn) return Promise.resolve();
try {
return Promise.resolve(fn(context, () => dispatch(index + 1)));
} catch (error) {
return Promise.reject(error);
}
}
return dispatch(0);
};
}
// 三组以上可直接运行的验证;异步题使用独立执行块。
(async () => {
const order = [];
const fn = compose([
async (_, next) => {
order.push(1);
await next();
order.push(6);
},
async (_, next) => {
order.push(2);
await next();
order.push(5);
},
async () => {
order.push(3);
await Promise.resolve();
order.push(4);
},
]);
await fn({});
console.log(order); // 预期:[1, 2, 3, 4, 5, 6]
console.log(await Promise.resolve(compose([
async (_, next) => {
await next();
await next();
},
])({})).then(() => false, () => true)); // 预期:true(拒绝)
console.log(await Promise.resolve(compose([
() => {
throw new Error("boom");
},
])({})).then(() => false, () => true)); // 预期:true(拒绝)
})().catch(console.error);
复杂度分析
时间复杂度:O(m)。调用链按顺序进入和退出 m 层中间件各一次。
空间复杂度:O(m)。嵌套异步调用链深度最多 m 层。