Giter VIP home page Giter VIP logo

learn-koa's Introduction

写在前面

为了更好的学习koa,但是以目前的水平去阅读koa源码可能还是力所不能及(虽然koa源码总共才1800行)。所以跟随教程来实现一个koa,下面会记录整个过程,供以后学习。

开始,准备工作,新建项目

koa官方是四个js文件:application.js、context.js、request.js、response.js。所以我这里也新建这个四个文件。

准备工作

原生的node启动一个服务

let http = require('http')
let server = http.createServer((req, res) => {
  res.end('hello world')
})
server.listen(3000);

koa启动一个服务

const koa = require('koa');
const app = new Koa();

app.use((ctx, next) => {
  ctx.body = 'Hello World';
});
app.listen(3000);
listen

koa的listen方法其实就是http的语法糖,本质上还是使用了http.createServer()。

ctx

ctx利用了上下文(context)机制,将原来的req,res合二为一,并进行了大量拓展。

use

koa的核心。

创建一个ctx

通过createContext创建一个ctx,我的理解就是讲request和response添加到ctx这个对象当中

createContext(req, res){
    // Object.create()方法是继承this.context,并且ctx增加属性是不影响原属性也就是this.context
    const ctx = Object.create(this.context);
    // 以前没用到过,相当于
    // const request = Object.create(this.request);
    // const ctx.request = Object.create(this.request);
    const request = ctx.request = Object.create(this.request);
    const response = ctx.response = Object.create(this.response);

    ctx.req = request.req = response.req = req;
    ctx.res = request.res = response.res = res;
    request.ctx = response.ctx = ctx;
    request.response = response;
    response.request = request;
    return ctx;
}

在app.js中

app.use((ctx)=>{
    console.log(ctx.req.url);              // 打印  '/'
    console.log(ctx.request.req.url);      // 打印  '/'
    // 这个时候还无法直接在ctx包括ctx.request取url等值
    console.log(ctx.url);                  // 打印  undefined
    console.log(ctx.request.url);          // 打印  undefined
})

从自定义的request对象取值,并拓展更多熟悉,如query、path等。直接从ctx取值

从自定义的request取值

let request = { 
    // get 用法为对象的访问器类似于下面
    // Object.defineProperty('name',{
    //    set:function(value){
    //          this.name = value;
    //    },
    //    get:function(){
    //        return this.name;
    //    }
    // })
    get url(){
        //这样可通过ctx.request.url 直接取值了。不需要再通过req
        return this.req.url;
    }
}

所以在app.js中

app.use((ctx)=>{
    console.log(ctx.req.url);              // 打印  '/'
    console.log(ctx.request.req.url);      // 打印  '/'
    console.log(ctx.url);                  // 打印  undefined
    console.log(ctx.request.url);          // 打印  '/'
})

直接从ctx取值

需要通过一个代理实现

修改context.js

let context = {}
// 创建一个defineGetter函数,参数分别是需要代理的对象和对象上的属性
function defineGetter(prop, name){
    // 每个对象都有一个__defineGetter__函数,可以用这个方法实现代理。
    context.__defineGetter__(name, function(){
        // 这里的this是ctx,为什么是ctx?
        return this[prop][name];
    })
}
defineGetter('request','url');
module.exports = context;

怎么实现代理?

**defineGetter**方法可以将一个函数绑定在当前对象的指定属性上,当那个属性的值被读取时,所绑定的函数就会被调用。 上诉例子:

context.__defineGetter__(name, function(){
        // 这里的this是ctx,为什么是ctx?
        return this[prop][name];
    })

defineGetter('request','url'); 当调用context.url时,出发绑定函数,return this.request.url; 由于ctx继承了context(在application.js中const ctx = Object.create(this.context))。所以当ctx.url,触发了绑定函数,返回this.request.url。(this即为ctx)

所以在app.js中

app.use((ctx)=>{
    console.log(ctx.req.url);              // 打印  '/'
    console.log(ctx.request.req.url);      // 打印  '/'
    console.log(ctx.url);                  // 打印  '/'
    console.log(ctx.request.url);          // 打印  '/'
})

实现ctx.body

koa的api,输出数据到页面既不是res.end('xxx')也不是res.send('xxx'),而是ctx.body = 'xxx'。

首先修改response

let response ={
    set body(value){
        this.res.statusCode = 200;
        this._body = value;
    },
    get body(){
        return this._body;
    }
}

在application.js中

handleRequest(req, res){
    // 创建ctx
    let ctx = this.createContext(req, res);
    // 封装好ctx后,通过回调参数返回给用户
    this.fn(ctx);
    // ctx.body用来输出页面,后面会如何说道如何绑定数据到ctx.body
    // 将ctx.response.body输出
    res.end(ctx.response.body);
}

在app.js中,可以直接设置ctx.response.body = 'hello world',这样就会输出hello world了

app.use((ctx)=>{
    console.log(ctx.req.url);
    console.log(ctx.request.req.url);
    // 通过自定义的request对象,可以直接通过ctx的request对象获取url了
    console.log(ctx.request.url);
    console.log(ctx.url);

    ctx.response.body = 'hello world';
    
})

这个时候是ctx.response.body,并不是ctx.body。同意通过contex代理一下

context.js

function defineSetter(prop, name){
    context.__defineSetter__(name,function(val){
        this[prop][name] = val;
    })
}
defineSetter('response','body');
defineGetter('response','body');

这个时候就可以直接使用ctx.body了。

实现多个use

实现多个use需要使用到一个compose函数,说通俗点就是一个递归函数,在第一个中间件函数的next回调调到去执行下一个中间件函数,当该next回调函数执行完后再执行完第一个中间件函数。

application.js

// 构造函数,原本只能一个,通过this.fn保存中间件函数,由于需要支持多个use,所以每次use都将函数保存到一个list中
// this.fn; 改成:
this.middlewares = [] // 按顺序存放中间件

// use函数
use(fn){
    // this.fn = fn;
    // 每次use将回调函数存进数组
    this.middlewares.push(fn);
}

// 最关键的compose函数,实际上是一个实现递归的函数。说实话还是挺神奇的
compose(middlewares, ctx){
    function dispatch(index){
        if(index == middlewares.length ) return ;  // 最后一次执行直接返回;
        let middleware = middlewares[index]        // 取出函数
        middleware(ctx, () => dispatch(index+1))   // 调用并传入ctx和下一个被调用的函数,用户通过next()时执行该函数
    }
    dispatch(0);
}

// handleRequest函数,调用compose函数
// this.fn(ctx); 多次调用use,所以改成;
this.compose(this.middlewares,ctx);

app.js

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)
})

执行:

1
3
5
/
/
/
/
6
4
2

把每个回调保证成Promise以实现异步

试试把中间件函数改成async函数之后,然后再看打印顺序,正确的打印顺序为1、3、5、6、5、2

app.use(async (ctx, next) => {
    console.log(1)
    await next()
    console.log(2)
})
app.use(async (ctx, next) => {
    console.log(3)
    let p = new Promise((resolve, roject) => {
        setTimeout(() => {
            console.log('3.5')
            resolve()
        }, 1000)
    })
    await p.then()
    await next()
    console.log(4)
})
app.use(async (ctx, next) => {
    console.log(5)
    next()
    console.log(6)
})

结果打印1、3、2、隔1秒 3.5、5、6、4

为什么会这样也是因为async函数的机制,await是一个让出线程的标志。await后面的函数会先执行一遍,然后就会跳出整个async函数来执行后面js栈的代码。

async function  demo() {
    await console.log('a');
    console.log('b');
}
demo();
console.log('c');
console.log('d');

打印顺序
a,c,d,b

解释:首先进入到第一个函数,执行

console.log(1)打印1,然后执行

await next(),然后跳出第一个函数,但是外面没有语句执行,所以进入第二个函数,执行

console.log(3)打印3,执行p的初始化(Promise新建后会立即执行),然后执行

await p.then(),执行p.then(),然后执行完后(由于是个异步,所以等待返回),跳出第二个函数,回到第一个函数,执行

console.log(2)打印2,然后回到第二个函数,等待p.then()返回,等待1s,

console.log(3.5)打印3.5,await返回后,才能执行下面的语句,

await next(),进入第三个函数,执行

console.log(5)打印5,然后执行,

await next(),没有next()函数了,则执行

console.log(5)打印5,然后回到第二个函数,执行下面的,

console.log(6)打印6。结束

而正确顺序应该是1、3、3.5、5、6、4、2。所以我们需要把每个回调函数封装成Promise

反过来想想为什么会出现1、3、2、3.5、5、6、4的顺序?

为什么把前面的注释掉了

因为我都解释错了。。。不过我现在明白了

learn-koa's People

Contributors

scyuan avatar

Watchers

James Cloos avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.