compose函数实现
- 每一个中间件需要封装一个
Promise
- 洋葱模型的先进后出操作,对应
Promise.resolve
的前后操作
const middleware1 = async (ctx, next) => {
console.log(1);
await next();
console.log(6);
};
const middleware2 = async (ctx, next) => {
console.log(2);
await next();
console.log(5);
};
const middleware3 = async (ctx, next) => {
console.log(3);
await next();
console.log(4);
};
const fn = compose([middleware1, middleware2, middleware3]);
fn().then(() => {
console.log("end");
});
function compose(middlewares) {
if (!Array.isArray(middlewares)) {
throw new TypeError("middle need a array");
}
return function (ctx, next) {
let index = -1;
// 第一次调用
return dispatch(0);
function dispatch(i) {
if (i < index) {
return Promise.reject(new Error("next() call multiple times"));
}
index = i;
let fn = middlewares[i];
if (i === middlewares.length) {
fn = next;
}
if (!fn) {
return Promise.resolve();
}
try {
return Promise.resolve(
fn(ctx, () => {
return dispatch(i + 1);
})
);
} catch (e) {
return Promise.reject(e);
}
}
};
}
// 结果显示
// "1"
// "2"
// "3"
// "4"
// "5"
// "6"
// "end"
结合http与events模块实现
const http = require("http");
const Emitter = require("events");
const context = {
_body: null,
get body() {
return this._body;
},
set body(val) {
this._body = val;
this.res.end(this._body);
},
};
function compose(middlewares) {
if (!Array.isArray(middlewares)) {
throw new TypeError("middle need a array");
}
return function (ctx, next) {
let index = -1;
// 第一次调用
return dispatch(0);
function dispatch(i) {
if (i < index) {
return Promise.reject(new Error("next() call multiple times"));
}
index = i;
let fn = middlewares[i];
if (i === middlewares.length) {
fn = next;
}
if (!fn) {
return Promise.resolve();
}
try {
return Promise.resolve(
fn(ctx, () => {
return dispatch(i + 1);
})
);
} catch (e) {
return Promise.reject(e);
}
}
};
}
class MyKoa extends Emitter {
constructor() {
super();
this.middleware = [];
this.context = Object.create(context);
}
/**
* 服务端监听
* @param {...any} args
*/
listen(...args) {
const server = http.createServer(this.callback());
return server.listen(...args);
}
/**
* 注册使用中间件
* @param {*} fn
*/
use(fn) {
if (typeof fn === "function") {
this.middleware.push(fn);
}
}
/**
* 中间件回调方法
*/
callback() {
let that = this;
if (this.listeners("error").length === 0) {
this.on("error", this.onerror);
}
const handleRequest = (req, res) => {
let context = this.createContext(req, res);
let middleware = this.middleware;
compose(middleware)(context).catch((err) => this.onerror(err));
};
return handleRequest;
}
/**
* 异常处理监听
* @param {EndOfStreamError} err
*/
onerror(err) {
console.log(err);
}
/**
* 创建通用上下文
* @param {Object} req
* @param {Object} res
*/
createContext(req, res) {
let context = Object.create(this.context);
context.req = req;
context.res = res;
return context;
}
}
const app = new MyKoa();
const PORT = 3001;
app.use(async (ctx) => {
ctx.body = "<p>this is a body</p>";
});
app.listen(PORT, () => {
console.log(`the web server is starting at port ${PORT}`);
});