资讯专栏INFORMATION COLUMN

理解Koa洋葱模型

yearsj / 2025人阅读

摘要:的嵌套就像是洋葱模型的形状就是一层包裹着一层,直到到最里面一层的的值返回。中间件引擎是有模块来实现的,也就是实现洋葱模型的核心引擎。表示遍历还没有结束。

中间件特性
    |                                                                                  |
    |                              middleware 1                                        |
    |                                                                                  |
    |          +-----------------------------------------------------------+           |
    |          |                                                           |           |
    |          |                    middleware 2                           |           |
    |          |                                                           |           |
    |          |            +---------------------------------+            |           |
    |          |            |                                 |            |           |
    | action   |  action    |        middleware 3             |    action  |   action  |
    | 001      |  002       |                                 |    005     |   006     |
    |          |            |   action              action    |            |           |
    |          |            |   003                 004       |            |           |
    |          |            |                                 |            |           |

+---------------------------------------------------------------------------------------------------->

    |          |            |                                 |            |           |
    |          |            |                                 |            |           |
    |          |            +---------------------------------+            |           |
    |          +-----------------------------------------------------------+           |
    +----------------------------------------------------------------------------------+

先写一段贯穿全文的koa的代码

const Koa = require("koa");
let app = new Koa();

const middleware1 = async (ctx, next) => { 
  console.log(1); 
  await next();  
  console.log(6);   
}

const middleware2 = async (ctx, next) => { 
  console.log(2); 
  await next();  
  console.log(5);   
}

const middleware3 = async (ctx, next) => { 
  console.log(3); 
  await next();  
  console.log(4);   
}

app.use(middleware1);
app.use(middleware2);
app.use(middleware3);
app.use(async(ctx, next) => {
  ctx.body = "hello world"
})

app.listen(3001)

// 输出1,2,3,4,5,6

await next()使每个middleware分成,前置操作,等待其他中间件操作可以观察到中间件的特性有:

上下文ctx

await next()控制前后置操作

后置操作类似于数据解构-栈,先进后出

promise 的模拟实现
Promise.resolve(middleware1(context, async() => {
  return Promise.resolve(middleware2(context, async() => {
    return Promise.resolve(middleware3(context, async() => {
      return Promise.resolve();
    }));
  }));
}))
.then(() => {
    console.log("end");
});

从这段模拟代码我们可以知道next()返回的是promise,需要使用await去等待promise的resolve值。promise的嵌套就像是洋葱模型的形状就是一层包裹着一层,直到await到最里面一层的promise的resolve值返回。

思考:

如果next()不加await执行顺序是什么呢?
在这个例子里面如果只是next()执行顺序跟await next()是一样的,因为next的前置操作是同步的

如果前置操作是异步的操作呢?

const p = function(args) {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log(args);
      resolve();
    }, 100);
  });
};

const middleware1 = async (ctx, next) => {
  await p(1);
  // await next();
  next();
  console.log(6);
};

const middleware2 = async (ctx, next) => {
  await p(2);
  // await next();
  next();
  console.log(5);
};

const middleware3 = async (ctx, next) => {
  await p(3);
  // await next();
  next();
  console.log(4);
};
// 输出结果:1,6,2,5,3,4

当程序执行到middleware1,执行到await p(1)等待promise值返回跳出然后到下一个事件循环时,执行next()也就是执行到middleware2,再执行到await p(2)等待promise值返回跳出middleware2,回到middleware1继续执行console.log(6),以此类推输出顺序为1.6.2.5.3.4

Promise的嵌套虽然可以实现中间件流程,但是嵌套的代码会产生可维护性和可读性的问题,也带来中间件扩展的问题。

Koa.js中间件引擎是有koa-compose模块来实现的,也就是Koa.js实现洋葱模型的核心引擎。

koa-compose 实现
  this.middleware = [];
  use(fn) {
    this.middleware.push(fn);
    ……
 }
 callback() {
    const fn = compose(this.middleware);
    ……
 }
 
function compose (middleware) {
  return function (context, next) {
    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, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

Koa实现的代码非常简洁,我们在使用use的时候将middleware存在一个数组里面,当拦截到请求时执行callback方法,callback中调用了compose,compose方法使用递归执行中间件,遍历完成返回promise.resolve(),实际最后执行的代码也是上面所讲的promise嵌套的形式。

扩展:Await与Generator

通常我们的都会说await阻塞后面的操作等待promise的resolve返回值或者其他值,如果没有await这个语法糖,要怎么去实现呢?这个等待的过程是怎么控制的呢?

Generator

Generator实际上是一个特殊的迭代器

let gen = null;
function* genDemo(){
  console.log(1)
  yield setTimeout(()=>{
    console.log(3);
    gen.next();// c
  },100)
  console.log(4)
}
gen = genDemo();// a
gen.next(); // b

a. 调用generator,该函数不执行,也就是还没有输出1,返回的是指向内部状态的遍历对象。
b. generator函数开始执行,输出1,遇到第一个yeild表达式停下来,调用gen.next()返回一个对象{value: 10, done:false},这里的value表示setTimeout的一个标识值,也就是调用clearTimeout的参数,是一个数字。done表示遍历还没有结束。100毫秒后输出3;
c. Generator函数从上次在yeild停止的地方一直执行到函数结束(没有其他的yeild),输出4,返回{value: undefined,done:true},表示遍历结束。

可以看到yeild有控制代码进度的作用,是不是跟await有异曲同工之妙
来看下await编译成generator形式的代码,虽然多了一些代码,但是我们可以把_asyncToGenerator(function*() {……}调用generator,把asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);看成是gen.next();就很容易理解了。

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
  try {
    var info = gen[key](arg);
    var value = info.value;
  } catch (error) {
    reject(error);
    return;
  }
  if (info.done) {
    resolve(value);
  } else {
    Promise.resolve(value).then(_next, _throw);
  }
}

function _asyncToGenerator(fn) {
  return function() {
    var self = this,
      args = arguments;
    return new Promise(function(resolve, reject) {
      var gen = fn.apply(self, args);
      function _next(value) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
      }
      function _throw(err) {
        asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
      }
      _next(undefined);
    });
  };
}

const middleware1 =
  /*#__PURE__*/
  (function() {
    var _ref = _asyncToGenerator(function*(ctx, next) {
      console.log(1);
      yield next();
      console.log(6);
    });

    return function middleware1(_x, _x2) {
      return _ref.apply(this, arguments);
    };
  })();

const middleware2 =
  /*#__PURE__*/
  (function() {
    var _ref2 = _asyncToGenerator(function*(ctx, next) {
      console.log(2);
      yield next();
      console.log(5);
    });

    return function middleware2(_x3, _x4) {
      return _ref2.apply(this, arguments);
    };
  })();

const middleware3 =
  /*#__PURE__*/
  (function() {
    var _ref3 = _asyncToGenerator(function*(ctx, next) {
      console.log(3);
      yield next();
      console.log(4);
    });

    return function middleware3(_x5, _x6) {
      return _ref3.apply(this, arguments);
    };
  })();

Promise.resolve(
  middleware1(
    context,
    /*#__PURE__*/
    _asyncToGenerator(function*() {
      return Promise.resolve(
        middleware2(
          context,
          /*#__PURE__*/
          _asyncToGenerator(function*() {
            return Promise.resolve(
              middleware3(
                context,
                /*#__PURE__*/
                _asyncToGenerator(function*() {
                  return Promise.resolve();
                })
              )
            );
          })
        )
      );
    })
  )
).then(() => {
  console.log("end");
});

参考链接:
https://chenshenhai.github.io...
https://segmentfault.com/a/11...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/106100.html

相关文章

  • KOA2框架原理解析和实现

    摘要:实现的四大模块上文简述了源码的大体框架结构,接下来我们来实现一个的框架,笔者认为理解和实现一个框架需要实现四个大模块,分别是封装创建类构造函数构造对象中间件机制和剥洋葱模型的实现错误捕获和错误处理下面我们就逐一分析和实现。 什么是koa框架?        koa是一个基于node实现的一个新的web框架,它是由express框架的原班人马打造的。它的特点是优雅、简洁、表达力强、自由度...

    tracymac7 评论0 收藏0
  • KOA2框架原理解析和实现

    摘要:实现的四大模块上文简述了源码的大体框架结构,接下来我们来实现一个的框架,笔者认为理解和实现一个框架需要实现四个大模块,分别是封装创建类构造函数构造对象中间件机制和剥洋葱模型的实现错误捕获和错误处理下面我们就逐一分析和实现。 什么是koa框架?        koa是一个基于node实现的一个新的web框架,它是由express框架的原班人马打造的。它的特点是优雅、简洁、表达力强、自由度...

    liangzai_cool 评论0 收藏0
  • 深入koa源码(二):核心库原理

    摘要:最近读了的源码,理清楚了架构设计与用到的第三方库。本系列将分为篇,分别介绍的架构设计和个核心库,最终会手动实现一个简易的。本文来自心谭博客深入源码核心库原理所有系列文章都放在了。这一段逻辑封装在了核心库里面。 最近读了 koa2 的源码,理清楚了架构设计与用到的第三方库。本系列将分为 3 篇,分别介绍 koa 的架构设计和 3 个核心库,最终会手动实现一个简易的 koa。这是系列第 2...

    tyheist 评论0 收藏0
  • 深入探析koa之中间件流程控制篇

    摘要:到此为止,我们就基本讲清楚了中的中间件洋葱模型是如何自动执行的。 koa被认为是第二代web后端开发框架,相比于前代express而言,其最大的特色无疑就是解决了回调金字塔的问题,让异步的写法更加的简洁。在使用koa的过程中,其实一直比较好奇koa内部的实现机理。最近终于有空,比较深入的研究了一下koa一些原理,在这里会写一系列文章来记录一下我的学习心得和理解。 在我看来,koa最核心...

    fuchenxuan 评论0 收藏0

发表评论

0条评论

yearsj

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<