Home  >  Article  >  Web Front-end  >  Analyze issues with the koa middleware mechanism in node

Analyze issues with the koa middleware mechanism in node

巴扎黑
巴扎黑Original
2017-08-23 13:59:061249browse

This article mainly introduces the detailed explanation of the koa middleware mechanism in node, and introduces koa and compatibility issues in detail. It has certain reference value. Those who are interested can learn more

koa

koa is a smaller, more expressive, and more robust web framework created by the original team of express.

In my eyes, koa is indeed much lighter than express. koa feels more like a middleware framework to me. koa is just a basic shelf. When you need to use the corresponding functions, use Just implement it with corresponding middleware, such as routing system, etc. A better point is that express is processed based on callbacks. As for how bad callbacks are, you can search and see by yourself. koa1 is based on the co library, so koa1 uses Generator instead of callbacks, and koa2 uses async/await due to node's support for async/await. Regarding async and co libraries, you can refer to an article I wrote before (Understanding async). Koa can be said to be a shelf for various middlewares. Let’s take a look at koa’s implementation of the middleware part:

koa1’s middleware

koa1 mainly uses It is implemented by Generator. Generally speaking, a middleware of koa1 probably looks like this:


app.use(function *(next){
  console.log(1);
  yield next;
  console.log(5);
});
app.use(function *(next){
  console.log(2);
  yield next;
  console.log(4);
});
app.use(function *(){
  console.log(3);
});

The output will be 1, 2, 3, 4 , 5. The implementation of koa's middleware mainly relies on koa-compose:


function compose(middleware){
 return function *(next){
  if (!next) next = noop();

  var i = middleware.length;
  // 组合中间件
  while (i--) {
   next = middleware[i].call(this, next);
  }

  return yield *next;
 }
}
function *noop(){}

The source code is very simple, and the function is to connect all the middleware together. First, pass a noop to the penultimate middleware as its next, and then pass the sorted penultimate middleware as next to the penultimate middleware. The final next is the sorted first middleware. pieces. It's more complicated to say, but let's look at it by drawing:

The effect achieved is as shown in the picture above, which is similar to the goal that redux needs to achieve. As long as the yield next is encountered, the next intermediate will be executed. It is easy to connect this process in series using the co library. Let’s briefly simulate the complete implementation of the middleware:


const middlewares = [];

const getTestMiddWare = (loggerA, loggerB) => {
  return function *(next) {
    console.log(loggerA);
    yield next;
    console.log(loggerB);
  }
};
const mid1 = getTestMiddWare(1, 4),
  mid2 = getTestMiddWare(2, 3);

const getData = new Promise((resolve, reject) => {
  setTimeout(() => resolve('数据已经取出'), 1000);
});

function *response(next) {
  // 模拟异步读取数据库数据
  const data = yield getData;
  console.log(data);
}

middlewares.push(mid1, mid2, response);
// 简单模拟co库
function co(gen) {
  const ctx = this,
    args = Array.prototype.slice.call(arguments, 1);
  return new Promise((reslove, reject) => {
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    if (!gen || typeof gen.next !== 'function') return resolve(gen);

    const baseHandle = handle => res => {
      let ret;
      try {
        ret = gen[handle](res);
      } catch(e) {
        reject(e);
      }
      next(ret);
    };
    const onFulfilled = baseHandle('next'),
      onRejected = baseHandle('throw');
      
    onFulfilled();
    function next(ret) {
      if (ret.done) return reslove(ret.value);
      // 将yield的返回值转换为Proimse
      let value = null;
      if (typeof ret.value.then !== 'function') {
        value = co(ret.value);
      } else {
        value = ret.value;
      }
      if (value) return value.then(onFulfilled, onRejected);
      return onRejected(new TypeError('yield type error'));
    }
  });
}
// 调用方式
const gen = compose(middlewares);
co(gen);

koa2’s middleware

With node's support for async/await, it seems that there is no need to resort to tool libraries like co. Just use the native ones directly, so koa has also made changes. Let's take a look at the current 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(&#39;next() called multiple times&#39;))
   index = i
   let fn = middleware[i]
   // 最后一个中间件的调用
   if (i === middleware.length) fn = next
   if (!fn) return Promise.resolve()
   // 用Promise包裹中间件,方便await调用
   try {
    return Promise.resolve(fn(context, function next () {
     return dispatch(i + 1)
    }))
   } catch (err) {
    return Promise.reject(err)
   }
  }
 }
}

koa-compose uses Promise. The parameters of koa2's middleware have also changed from one to two, and the middleware that executes the next one uses await. next(), to achieve the same effect as the above sample code, you need to change the way the middleware is written:


const middlewares = [];
const getTestMiddWare = (loggerA, loggerB) => async (ctx, next) => {
  console.log(loggerA);
  await next();
  console.log(loggerB);
};

const mid1 = getTestMiddWare(1, 4),
  mid2 = getTestMiddWare(2, 3);
const response = async () => {
  // 模拟异步读取数据库数据
  const data = await getData();
  console.log(data);
};
const getData = () => new Promise((resolve, reject) => {
  setTimeout(() => resolve(&#39;数据已经取出&#39;), 1000);
});
middlewares.push(mid1, mid2);

// 调用方式
compose(middlewares)(null, response);

How to achieve compatibility

It can be seen that koa1 and koa2 still have many differences in the implementation of middleware. If you use the middleware of koa1 directly under koa2, there will definitely be errors. How to make these two versions compatible? It has also become a problem. The koa team has written a package that is a middleware for koa1 that can be used in koa2. It is called koa-convert. Let’s first take a look at how to use this package:


function *mid3(next) {
  console.log(2, &#39;koa1的中间件&#39;);
  yield next;
  console.log(3, &#39;koa1的中间件&#39;);
}
convert.compose(mid3)

Let’s take a look at the idea of ​​​​implementing this package:


// 将参数转为数组,对每一个koa1的中间件执行convert操作
convert.compose = function (arr) {
 if (!Array.isArray(arr)) {
  arr = Array.from(arguments)
 }
 return compose(arr.map(convert))
}
// 关键在于convert的实现
const convert = mw => (ctx, next) => {
  // 借助co库,返回一个Promise,同时执行yield
  return co.call(ctx, mw.call(ctx, createGenerator(next)));
};

function * createGenerator (next) {
 /*
   next为koa-compomse中:
   function next () {
     return dispatch(i + 1)
   }
 */
 return yield next()
 // 执行完koa1的中间件,又回到了利用await执行koa2中间件的正轨
}

Personally feel that the idea of ​​koa-convert is to encapsulate a layer of Promise for Generator so that the previous middleware can Use the await next() method to call, and use the co library for the execution of Generator to achieve compatibility.

The above is the detailed content of Analyze issues with the koa middleware mechanism in node. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn