浅析koa的洋葱模子完成

媒介

koa被认为是第二代node web framework,它最大的特性就是奇特的中间件流程掌握,是一个典范的洋葱模子。koa和koa2中间件的思绪是一样的,然则完成体式格局有所区别,koa2在node7.6以后更是能够直接用async/await来替换generator运用中间件,本文以末了一种状况举例。

洋葱模子

下面两张图是网上找的,很清楚的表清楚明了一个要求是怎样经由中间件末了天生相应的,这类形式中开辟和运用中间件都是异常轻易的
《浅析koa的洋葱模子完成》《浅析koa的洋葱模子完成》

来看一个koa2的demo:

const Koa = require('koa');

const app = new Koa();
const PORT = 3000;

// #1
app.use(async (ctx, next)=>{
    console.log(1)
    await next();
    console.log(1)
});
// #2
app.use(async (ctx, next) => {
    console.log(2)
    await next();
    console.log(2)
})

app.use(async (ctx, next) => {
    console.log(3)
})

app.listen(PORT);
console.log(`http://localhost:${PORT}`);

接见http://localhost:3000,掌握台打印:

1
2
3
2
1

怎样,是否是有一点点觉得了。当顺序运转到await next()的时刻就会停息当前顺序,进入下一个中间件,处置惩罚完以后才会仔回过头来继承处置惩罚。也就是说,当一个要求进入,#1会被第一个和末了一个经由,#2则是被第二和倒数第二个经由,顺次类推。

完成

koa的完成有几个最主要的点

  1. context的保留和通报
  2. 中间件的治理和next的完成

翻看源码我们发明
app.listen运用了this.callback()来天生node的httpServer的回调函数

listen(...args) {
    debug('listen');
    const server = http.createServer(this.callback());
    return server.listen(...args);
}

那就再来看this. callback

callback() {
    const fn = compose(this.middleware);
    
    if (!this.listeners('error').length) this.on('error', this.onerror);
    
    const handleRequest = (req, res) => {
      const ctx = this.createContext(req, res);
      return this.handleRequest(ctx, fn);
    };
    
    return handleRequest;
}

这里用compose处置惩罚了一下this.middleware,创建了ctx并赋值为createContext的返回值,末了返回了handleRequest

this.middleware看起来应该是中间件的鸠合,查了下代码,果不其然:

this.middleware = [];
use(fn) {
    if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
    if (isGeneratorFunction(fn)) {
      deprecate('Support for generators will be removed in v3. ' +
                'See the documentation for examples of how to convert old middleware ' +
                'https://github.com/koajs/koa/blob/master/docs/migration.md');
      fn = convert(fn);
    }
    debug('use %s', fn._name || fn.name || '-');
    this.middleware.push(fn);
    return this;
}

抛开兼容和推断,这段代码只做了一件事:

use(fn) {
    this.middleware.push(fn);
    return this;
}

本来当我们app.use的时刻,只是把要领存在了一个数组里。
那末compose 又是什么呢。跟踪源码能够看到compose来自koa-compose模块,代码也不多:(去掉了一些不影响主逻辑的推断)

function compose (middleware) {
  return function (context, next) {
    // last called middleware #
    let index = -1
    return dispatch(0)
    
    function dispatch (i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i]
      if (i === middleware.length) fn = next
      if (!fn) return Promise.resolve()
      try {
        return Promise.resolve(fn(context, function next () {
          return dispatch(i + 1)
        }))
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

比较症结的就是这个dispatch函数了,它将遍历全部middleware,然后将contextdispatch(i + 1)传给middleware中的要领。

return Promise.resolve(fn(context, function next () {
      return dispatch(i + 1)
}))

这段代码就很奇妙的完成了两点:

1. 将`context`一起传下去给中间件

2. 将`middleware`中的下一个中间件`fn`作为将来`next`的返回值

这两点也是洋葱模子完成的中心。
再往下看代码实际上就没有太多名堂了。
createContexthandleRequest 做的事实际上是把ctx和中间件举行绑定,也就是第一次挪用compose 返回值的处所。

  createContext(req, res) {
    const context = Object.create(this.context);
    const request = context.request = Object.create(this.request);
    const response = context.response = Object.create(this.response);
    context.app = request.app = response.app = this;
    context.req = request.req = response.req = req;
    context.res = request.res = response.res = res;
    request.ctx = response.ctx = context;
    request.response = response;
    response.request = request;
    context.originalUrl = request.originalUrl = req.url;
    context.cookies = new Cookies(req, res, {
      keys: this.keys,
      secure: request.secure
    });
    request.ip = request.ips[0] || req.socket.remoteAddress || '';
    context.accept = request.accept = accepts(req);
    context.state = {};
    return context;
  }

  handleRequest(ctx, fnMiddleware) {
    const res = ctx.res;
    res.statusCode = 404;
    const onerror = err => ctx.onerror(err);
    const handleResponse = () => respond(ctx);
    onFinished(res, onerror);
    return fnMiddleware(ctx).then(handleResponse).catch(onerror);
  }


《浅析koa的洋葱模子完成》

    原文作者:我有一只小腊肠
    原文地址: https://segmentfault.com/a/1190000013981513
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞