Анализ исходного кода Koa

Node.js JavaScript

В прошлой статье писали о том, как читать исходный код Koa, и вкратце пробежались по исходному коду Koa, но как то конкретного вывода никто не сделал, принцип работы промежуточного ПО не ясен, Здесь мы пройдемся по исходный код Коа внимательно.

Пройтись по примеру

Сначала рассмотрим пример

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

app.use(async ctx => {
  ctx.body = 'Hello World';
});

app.listen(3000);

Запускаем веб-сервис Hello World в качестве переупаковки http-модуля, давайте потихоньку копаться в том, как он упакован (неактуальный код я удалю).

прежде всегоlisten:

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

Мы все знаем, что модуль http — это не что иное, какhttp.createServer(fn).listen(port), где fn несет в себе req, res. По приведенному выше пакету мы можем быть увереныthis.callbackОн должен быть с запросом и ответом.Тогда давайте посмотримthis.callbackБар.

  callback() {
    const fn = compose(this.middleware);
    const handleRequest = (req, res) => {
      const ctx = this.createContext(req, res);
      return this.handleRequest(ctx, fn);
    };

    return handleRequest;
  }

В самом делеcallbackВозвращаемая функция с req, res, дальше буду смотреть внизhandleRequestчто случилось,ctxПоявился здоровяк.При использовании коа все запросы и ответы вешаются на ctx.Похоже ctx передается черезcreateContextСоздал, тогда продолжайте читатьcreateContextБар:

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

createContextЭто относительно просто, то есть навешивать разные полезные и бесполезные переменные на контекст, код тоже очень простой, но поскольку он включает в себя запрос и ответ, нам нужно бросить беглый взгляд на request.js и response.js:

module.exports = {
  get header() {
    return this.req.headers;
  },
//..more items
}

Получить переменные очень просто, и говорить не о чем, поэтому вернитесь к предыдущей части обратного вызова, ctx создается, затем вызывается и возвращается.this.handleReques, нечего сказать, продолжайте читать:

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

Эта часть немного сложнее, как видно из вышеfnMiddlewareЭто промежуточное программное обеспечение, которое мы вынули, а затем мы передаем ctx промежуточному программному обеспечению для выполнения, что немного похоже на наше обычное использование.На данный момент суть такова:промежуточное ПО

промежуточное ПО

Прежде чем исследовать принцип промежуточного программного обеспечения, давайте посмотрим, как используется промежуточное программное обеспечение.Вот простой пример:

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

app.use(async function m1 (ctx, nex) {
   console.log('m1')
   await next()
   console.log('m2 end')
})

app.use(async function m2 (ctx, nex) {
  console.log('m2')
  await next()
  console.log('m2 end')
})

app.use(async function m3 (ctx, nex) {
  console.log('m3')
  ctx.body = 'Hello World'
})

Приведенные выше результаты довольно ясны, но давайте визуализируем их:

m1: 输出m1
await1: m1你先暂停一下让m2先走
m1: ...
m2: 输出m2
await2: m2你也停一下让m3先走
m2: ...(委屈)
m3: 输出m3, 上面的注意啦要远路返回了
m2: 输出m2 end m1注意了我要返回啦
m1: 输出m1 end

respond: ctx.body是Hello world呢 就糊弄一下用户返回吧

Видите ли, ctx.body не означает немедленный ответ, это просто переменная, которую мы будем использовать позже, то есть наш ctx проходит через все промежуточное программное обеспечение, прежде чем ответить Я не упоминаю здесь магическую паузу ожидания. эффект, нам просто нужно иметь возможность использовать его вот так. Итак, как наше промежуточное ПО реализует это? Давайте посмотрим на compose.js:

function compose (middleware) {

  /**
   * @param {Object} context
   * @return {Promise}
   * @api public
   */

  return function (context, next) {
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      index = i
      let fn = middleware[i]
      if (!fn) return Promise.resolve()
      return Promise.resolve(fn(context, function next () {
          return dispatch(i + 1)
        }))
    }
  }
}

Те, кто читал мою предыдущую статью, могут знать, что на самом деле это рекурсия. Но в отличие от рекурсии connect, это Promise. Все мы знаем, что await и Promise вкуснее. Дело в том, что это next, после того как мы вызываем await next, The программа должна дождаться выполнения этого Promise.Давайте упростим модель промежуточного программного обеспечения:

Promise.resolve(async m1 () {
  console.log(m1)
  await Promise.resolve(async m2 () {
    console.log(m2)
    await Promise.resolve(async m3 () {
      console.log(m3)
      ctx.body = 'xxx'
     })
     console.log(m2 end)
  })
  console.log(m1 end)
})

Понятно ли, что на прикладном уровне нам не нужно думать о том, как реализуется async/await, просто нужно понять, какой эффект он дает.

Еще надо полюбоваться на великого бога tj.Если будут вопросы,можете пообщаться друг с другом.

image