基於Node.js平臺的下一代Web開發框架數組
Koa的基本用法比較簡單,並且沒有捆綁任何中間件bash
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
複製代碼
由Koa的用法能夠看出Koa自己是一個類,有use和listen方法,廢話很少說,上代碼。 Koa基本代碼結構,核心代碼文件 application.js app
測試文件test.js框架
let Koa = require('./koa/application')
let app = new Koa()
app.use((req,res)=>{
res.end('jeffywin')
})
app.listen(3000)
複製代碼
application.jskoa
/**
* 核心方法 use listen handleRequest
*/
let http = require('http')
class Koa {
constructor(){
this.callbackFn;
}
//test.js中的use回調傳給cb,而後存到this.callbackFn中
use(cb) {
this.callbackFn = cb;
}
//1.test.js中use方法中要如何能拿到req,res,首先handleRequest接收listen中的req,res
//2.接着req,res傳給this.callbackFn,也就是上面use方法中賦值的this.callbackFn
handleRequest(req,res){
console.log(req,res)
this.callbackFn(req,res)
}
//listen方法中接收test.js listen方法傳遞過來的參數,接着將req,res傳給方法handleRequest
listen(...args) {
let server = http.createServer(this.handleRequest.bind(this))
server.listen(...args)
}
}
module.exports = Koa;
複製代碼
更新test.js中use方法,實現下面4個ctx中的屬性異步
let Koa = require('./koa/application')
let app = new Koa()
//ctx是對原生req,res的封裝
app.use((ctx,next)=>{
console.log(ctx.req.path) //ctx.req = req
console.log(ctx.request.req.path) //ctx.request.req = req
console.log(ctx.request.path) //ctx.request 是Koa本身封裝的
console.log(ctx.path) //ctx代理ctx.request ctx.path === ctx.request.path
ctx.body = 'jeffywin'
})
app.listen(3000)
複製代碼
application.jsasync
/**
* 核心方法 use listen handleRequest
*/
let http = require('http')
let Stream = require('stream');
let context = require('./context')
let response = require('./response')
let request = require('./request')
class Koa {
constructor(){
this.callbackFn;
this.context = context
this.response = response
this.request = request
}
//test.js中的use回調傳給cb,而後存到this.callbackFn中
use(cb) {
this.callbackFn = cb;
}
createContext(req,res) {
//ctx能夠拿到context的屬性,但又不修改context,經過Object.create()至關於建立匿名函數
let ctx = Object.create(this.context)
ctx.request = Object.create(this.request)
ctx.response = Object.create(this.response)
ctx.req = ctx.request.req = req//ctx本身添加屬性request
ctx.res = ctx.response.res = res//ctx本身添加屬性response
return ctx
}
//1.use方法中要能拿到req,res,首先handleRequest接收listen中的req,req
//2.接着req,res傳給this.createContext獲得ctx,也就是上面use方法中賦值的this.callbackFn
handleRequest(req,res){
res.statusCode = 404
//建立上下文
let ctx = this.createContext(req,res)
this.callbackFn(ctx)
let body = ctx.body
if (body instanceof Stream){
body.pipe(res);
}else if(Buffer.isBuffer(body) || typeof body === 'string'){
res.end(body);
}else if(typeof body === 'object' ){
res.end(JSON.stringify(body));
}else{
res.end(body);
}
}
//listen方法中接收test.js listen方法傳遞過來的參數,接着將req,res傳給方法handleRequest
listen(...args) {
let server = http.createServer(this.handleRequest.bind(this))
server.listen(...args)
}
}
module.exports = Koa;
複製代碼
request.js函數
//ctx.request.url = ctx.url
let url = require('url')
let request = {
//get方法至關於Object.defineProperty()的get方法的簡寫,只要調ctx.request的url方法
//就會返回this.req.url,好比ctx.request.url,此時this == ctx.request
//當調用ctx.request.url 就至關於調用 ctx.request.req.url,然後者在application.js中能夠拿到
get url(){
return this.req.url
},
get path() {
return url.parse(this.req.url).pathname
}
}
module.exports = request;
複製代碼
response.js測試
let response = {
set body(val) {
this.res.statusCode = 200
this._body = val
},
get body(){
return this._body
}
}
module.exports = response;
複製代碼
context.jsui
//怎麼直接ctx.url;方法,用ctx代理ctx.request,用原生方法__defineGetter__進行封裝
let proto = {}
function defineGetter(property,name) {
proto.__defineGetter__(name,function(){
return this[property][name]
})
}
function defineSetter(property,name) {
proto.__defineSetter__(name,function(val){
this[property][name] = val
})
}
//調用ctx.url 至關於調用this[property][name],也就是this.request.url
defineGetter('request','url')
defineGetter('request','path')//ctx.path = ctx.request.path
defineGetter('response','body')//ctx.body = ctx.response.body
defineSetter('response','body')
module.exports = proto;
複製代碼
test.js
let Koa = require('./koa/application')
let app = new Koa()
app.use((ctx,next)=>{
console.log(1)
next()
console.log(2)
})
app.use((ctx,next)=>{
console.log(3)
next()
console.log(4)
})
app.use((ctx,next)=>{
console.log(5)
next()
console.log(6)
})
app.listen(3000)
複製代碼
next()方法至關於下一個執行函數
按照Koa源碼 應該是 1 => 3 => 5 => 6 => 4 => 2,相似洋蔥模型,以下圖所示 可是上述代碼中use中若是有異步,就有問題// 模擬Koa中間件實現原理
let fs = require('fs');
function app() = {}
app.middlewares = []//聲明一個數組
app.use = function(cb){
app.middlewares.push(cb)
}
app.use(async (ctx, next) => {
console.log(1);
await next();
console.log(2);
});
app.use(async (ctx, next) => {
console.log(3);
await next();
console.log(4);
});
app.use(async (ctx, next) => {
console.log(5);
await next();
console.log(6);
});
function dispatch(index){
if(index === add.middlewares.length) return
let middleware = app.middlewares[index]
middle({},()=>dispatch(index+1))//遞歸調用下一個中間件,next()
}
dispatch(0)//讓第一個中間件執行
複製代碼
回到test.js中
let Koa = require('./koa/application')
let app = new Koa()
//Koa可使用 async await
//第一個use中使用了await next(),此時next是第二個use中的 async函數,而且因爲是await,因此第二個use中返回的是Promise,須要等待執行完才能夠執行下面代碼,同理,第二個next要等待第三個函數執行完,這些中間件會造成一個Promise鏈
let log = () => {
return new Promise((resolve,reject) =>{
setTimeout(()=>{
console.log('Ok')
resolve()
},1000)
})
}
app.use(async (ctx, next) => {
console.log(1);
await next();
console.log(2);
});
app.use(async (ctx, next) => {
console.log(3);
await log();
await next();
console.log(4);
});
app.use(async (ctx, next) => {
console.log(5);
await next();
console.log(6);
ctx.body = 'jeffywin'
});
app.on('error', err => {
console.log(err);
})
app.listen(3000);
// 1 3 Ok 5 6 4 2
複製代碼
最終版application.js
let http = require('http')
let Stream = require('stream');
let EventEmitter = require('events');
let context = require('./context')
let response = require('./response')
let request = require('./request')
class Koa extends EventEmitter{
constructor(){
super();
this.middlewares = []
this.context = Object.create(context);
this.request = Object.create(request);
this.response = Object.create(response);
}
use(cb) {
this.middlewares.push(cb)
}
createContext(req,res) {
//ctx能夠拿到context的屬性,但又不修改context,經過Object.create()至關於建立匿名函數
let ctx = Object.create(this.context)
ctx.request = Object.create(this.request)
ctx.response = Object.create(this.response)
ctx.req = ctx.request.req = req//ctx本身添加屬性request
ctx.res = ctx.response.res = res//ctx本身添加屬性response
return ctx
}
//核心方法 compose
compose(ctx,middlewares){
function dispatch(index) {
if(index === middlewares.length) return Promise.resolve();
let fn = middlewares[index];
return Promise.resolve(fn(ctx,()=>dispatch(index+1)));
}
return dispatch(0);
}
//1.use方法中要能拿到req,res,首先handleRequest接收listen中的req,res
//2.接着req,res傳給this.createContext獲得ctx
handleRequest(req,res){
res.statusCode = 404
//建立上下文
let ctx = this.createContext(req,res)
let composeMiddleware = this.compose(ctx, this.middlewares)//返回的確定是Promise
//當Promise都執行完後,執行成功回調
composeMiddleware.then(()=>{
let body = ctx.body
if (body instanceof Stream){
body.pipe(res);
}else if(Buffer.isBuffer(body) || typeof body === 'string'){
res.end(body);
}else if(typeof body === 'object' ){
res.end(JSON.stringify(body));
}else{
res.end(body);
}
}).catch(err => {
this.emit('error', err);
});
}
//listen方法中接收test.js listen方法傳遞過來的參數,接着將req,res傳給方法handleRequest
listen(...args) {
let server = http.createServer(this.handleRequest.bind(this))
server.listen(...args)
}
}
module.exports = Koa;
複製代碼