傻瓜式解读koa中间件处理模块koa-compose的使用

最近需要单独使用到koa-compose这个模块,虽然使用koa的时候大致知道中间件的执行流程,但是没仔细研究过源码用起来还是不放心(主要是这个模块代码少,多的话也没兴趣去研究了)。

koa-compose看起来代码少,但是确实绕。闭包,递归,Promise。。。看了一遍脑子里绕不清楚。看了网上几篇解读文章,都是针对单行代码做解释,还是绕不清楚。最后只好采取一种傻瓜的方式:

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, dispatch.bind(null, i + 1)));
   } catch (err) {
    return Promise.reject(err)
   }
  }
 }
}

写出如下代码:

var index = -1;
function compose() {
  return dispatch(0)
}
function dispatch (i) {
   if (i <= index) return Promise.reject(new Error('next() called multiple times'))
   index = i
   var fn = middleware[i]
   if (i === middleware.length) fn = next
   if (!fn) return Promise.resolve('fn is undefined')
   try {
    return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
   } catch (err) {
    return Promise.reject(err)
   }
 }

 function f1(context,next){
  console.log('middleware 1');
  next().then(data=>console.log(data));
  console.log('middleware 1');
  return 'middleware 1 return';
 }
 function f2(context,next){
  console.log('middleware 2');
  next().then(data=>console.log(data));
  console.log('middleware 2');
  return 'middleware 2 return';
 }
 function f3(context,next){
  console.log('middleware 3');
  next().then(data=>console.log(data));
  console.log('middleware 3');
  return 'middleware 3 return';
 }
var middleware=[
 f1,f2,f3
]

var context={};
var next=function(context,next){
  console.log('middleware 4');
  next().then(data=>console.log(data));
  console.log('middleware 4');
  return 'middleware 4 return';
};
compose().then(data=>console.log(data));

直接运行结果如下:

"middleware 1"

"middleware 2"

"middleware 3"

"middleware 4"

"middleware 4"

"middleware 3"

"middleware 2"

"middleware 1"

"fn is undefined"

"middleware 4 return"

"middleware 3 return"

"middleware 2 return"

"middleware 1 return"

按着代码运行流程一步步分析:

dispatch(0)

i==0,index==-1 i>index 往下

index=0

fn=f1

Promise.resolve(f1(context, dispatch.bind(null, 0 + 1)))

这就会执行

f1(context, dispatch.bind(null, 0 + 1))

进入到f1执行上下文

console.log('middleware 1');

输出middleware 1

next()

其实就是调用dispatch(1) bind的功劳

递归开始

dispatch(1)

i==1,index==0 i>index 往下

index=1

fn=f2

Promise.resolve(f2(context, dispatch.bind(null, 1 + 1)))

这就会执行

f2(context, dispatch.bind(null, 1 + 1))

进入到f2执行上下文

console.log('middleware 2');

输出middleware 2

next()

其实就是调用dispatch(2)

接着递归

dispatch(2)

i==2,index==1 i>index 往下

index=2

fn=f3

Promise.resolve(f3(context, dispatch.bind(null, 2 + 1)))

这就会执行

f3(context, dispatch.bind(null, 2 + 1))

进入到f3执行上下文

console.log('middleware 3');

输出middleware 3

next()

其实就是调用dispatch(3)

接着递归

dispatch(3)

i==3,index==2 i>index 往下

index=3

i === middleware.length

fn=next

Promise.resolve(next(context, dispatch.bind(null, 3 + 1)))

这就会执行

next(context, dispatch.bind(null, 3 + 1))

进入到next执行上下文

console.log('middleware 4');

输出middleware 4

next()

其实就是调用dispatch(4)

接着递归

dispatch(4)

i==4,index==3 i>index 往下

index=4

fn=middleware[4]

fn=undefined

reuturn Promise.resolve('fn is undefined')

回到next执行上下文

console.log('middleware 4');

输出middleware 4

return 'middleware 4 return'
Promise.resolve('middleware 4 return')

回到f3执行上下文

console.log('middleware 3');

输出middleware 3

return 'middleware 3 return'
Promise.resolve('middleware 3 return')

回到f2执行上下文

console.log('middleware 2');

输出middleware 2

return 'middleware 2 return'
Promise.resolve('middleware 2 return')

回到f1执行上下文

console.log('middleware 1');

输出middleware 1

return 'middleware 1 return'
Promise.resolve('middleware 1 return')

回到全局上下文

至此已经输出

"middleware 1"

"middleware 2"

"middleware 3"

"middleware 4"

"middleware 4"

"middleware 3"

"middleware 2"

"middleware 1"

那么

"fn is undefined"

"middleware 4 return"

"middleware 3 return"

"middleware 2 return"

"middleware 1 return"

怎么来的呢

回头看一下,每个中间件里都有

next().then(data=>console.log(data));

按照之前的分析,then里最先拿到结果的应该是next中间件的,而且结果就是Promise.resolve('fn is undefined')的结果,然后分别是f4,f3,f2,f1。那么为什么都是最后才输出呢?

Promise.resolve('fn is undefined').then(data=>console.log(data));
console.log('middleware 4');

运行一下就清楚了

或者

setTimeout(()=>console.log('fn is undefined'),0);
console.log('middleware 4');

整个调用过程还可以看成是这样的:

function composeDetail(){
 return Promise.resolve(
  f1(context,function(){
   return Promise.resolve(
    f2(context,function(){
     return Promise.resolve(
      f3(context,function(){
       return Promise.resolve(
        next(context,function(){
         return Promise.resolve('fn is undefined')
        })
       )
      })
     )
    })
   )
  })
 )
}
composeDetail().then(data=>console.log(data));

方法虽蠢,但是compose的作用不言而喻了

最后,if (i <= index) return Promise.reject(new Error('next() called multiple times'))这句代码何时回其作用呢?

一个中间件里调用两次next(),按照上面的套路走,相信很快就明白了。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 深入理解 Koa 框架中间件原理

    Node 主要用在开发 Web 应用,koa 是目前 node 里最流行的 web 框架. 在 Node 开启一个 http 服务简直易如反掌,官网 demo. const http = require("http"); const server = http.createServer((req, res) => { res.statusCode = 200; res.setHeader("Content-Type", "text/plain&quo

  • 浅谈redux, koa, express 中间件实现对比解析

    如果你有 express ,koa, redux 的使用经验,就会发现他们都有 中间件(middlewares)的概念,中间件 是一种拦截器的思想,用于在某个特定的输入输出之间添加一些额外处理,同时不影响原有操作. 最开始接触 中间件是在服务端使用 express 和 koa 的时候,后来从服务端延伸到前端,看到其在redux的设计中也得到的极大的发挥.中间件的设计思想也为许多框架带来了灵活而强大的扩展性. 本文主要对比redux, koa, express 的中间件实现,为了更直观,我会抽取出

  • 详解KOA2如何手写中间件(装饰器模式)

    前言 Koa 2.x 版本是当下最流行的 NodeJS 框架, Koa 2.0 的源码特别精简,不像 Express 封装的功能那么多,所以大部分的功能都是由 Koa 开发团队(同 Express 是一家出品)和社区贡献者针对 Koa 对 NodeJS 的封装特性实现的中间件来提供的,用法非常简单,就是引入中间件,并调用 Koa 的 use 方法使用在对应的位置,这样就可以通过在内部操作 ctx 实现一些功能,我们接下来就讨论常用中间件的实现原理以及我们应该如何开发一个 Koa 中间件供自己和别

  • node中koa中间件机制详解

    koa koa是由express原班人马打造的一个更小.更富有表现力.更健壮的web框架. 在我眼中,koa的确是比express轻量的多,koa给我的感觉更像是一个中间件框架,koa只是一个基础的架子,需要用到的相应的功能时,用相应的中间件来实现就好,诸如路由系统等.一个更好的点在于,express是基于回调来处理,至于回调到底有多么的不好,大家可以自行搜索来看.koa1基于的co库,所以koa1利用Generator来代替回调,而koa2由于node对async/await的支持,所以koa

  • Koa日志中间件封装开发详解

    对于一个服务器应用来说,日志的记录是必不可少的,我们需要使用其记录项目程序每天都做了什么,什么时候发生过错误,发生过什么错误等等,便于日后回顾.实时掌握服务器的运行状态,还原问题场景. 日志的作用 记录服务器程序运行状态: 帮助开发者快速捕获错误,定位以及决解故障. 日志中间件开发工具log4js 在node当中没有自带的日志模块,所以需要使用第三方模块 使用模块:log4js 安装: npm i log4js -S logsjs官方文档 日志分类: 访问日志: 记录客户端对项目的访问,主要是

  • 深入解析koa之中间件流程控制

    前言 koa被认为是第二代web后端开发框架,相比于前代express而言,其最大的特色无疑就是解决了回调金字塔的问题,让异步的写法更加的简洁.在使用koa的过程中,其实一直比较好奇koa内部的实现机理.最近终于有空,比较深入的研究了一下koa一些原理,在这里会写一系列文章来记录一下我的学习心得和理解. 在我看来,koa最核心的函数是大名鼎鼎的co,koa正是基于这个函数实现了异步回调同步化,以及中间件流程控制.当然在这篇文章中我并不会去分析co源码,我打算在整个系列文章中,一步一步讲解如何实现

  • 傻瓜式解读koa中间件处理模块koa-compose的使用

    最近需要单独使用到koa-compose这个模块,虽然使用koa的时候大致知道中间件的执行流程,但是没仔细研究过源码用起来还是不放心(主要是这个模块代码少,多的话也没兴趣去研究了). koa-compose看起来代码少,但是确实绕.闭包,递归,Promise...看了一遍脑子里绕不清楚.看了网上几篇解读文章,都是针对单行代码做解释,还是绕不清楚.最后只好采取一种傻瓜的方式: koa-compose去掉一些注释,类型校验后,源码如下: function compose (middleware) {

  • koa中间件核心(koa-compose)源码解读分析

    最近经常使用koa进行服务端开发,迷恋上了koa的洋葱模型,觉得这玩意太好用了.而且koa是以精简为主,没有很多集成东西,所有的东西都需按需加载,这个更是太合我胃口了哈哈哈哈. 相对与express的中间件,express的中间件使用的是串联,就像冰糖葫芦一样一个接着一个,而koa使用的V型结构(洋葱模型),这将给我们的中间件提供更加灵活的处理方式. 基于对洋葱模型的热衷,所以对koa的洋葱模型进行一探究竟,不管是koa1还是koa2的中间件都是基于koa-compose进行编写的,这种V型结构

  • 详解express与koa中间件模式对比

    起因 最近在学习koa的使用, 由于koa是相当基础的web框架,所以一个完整的web应用所需要的东西大都以中间件的形式引入,比如koa-router, koa-view等.在koa的文档里有提到:koa的中间件模式与express的是不一样的,koa是洋葱型,express是直线型,至于为什么这样,网上很多文章并没有具体分析.或者简单的说是async/await的特性之类.先不说这种说法的对错,对于我来说这种说法还是太模糊了.所以我决定通过源码来分析二者中间件实现的原理以及用法的异同. 为了简

  • node.js express和koa中间件机制和错误处理机制

    目录 一.前言 二.中间件问题解析 三.错误逻辑捕获 3.1 express的错误捕获逻辑 3.2 同步逻辑错误获取的底层逻辑 3.3 异步逻辑错误获取的底层逻辑 3.4 koa的错误获取逻辑 一.前言 大家可能都知道koa是express核心原班人马写的,那么他们为什么要在express后再造一个koa的轮子呢? 今天就给大家带来一些分析.希望能够起到一个抛砖引玉的作用. 其实,这个题目也可以这么问, express有什么缺点? koa解决了一些express的什么问题? 这也在一些面试题中会

  • 人人都是开发者:7款傻瓜式APP开发工具

    谷歌推出的App Inventor Android App开发工具可以让你仅通过拖拉式的简单操作就可以创建自己的Android App.对于那些为了特定目的想要动手尝试开发一个简单应用的用户,除了App Inventor其实还有很多其他的选择,比如下面介绍的5款工具就可以让所有人都能轻松开发手机应用.不过如果你真想做个专业的开发者,还是像lion一样踏踏实实从基础学起比较好. 国外的营销工作者和技术开发者开发了许多能帮助企业创建简单APP应用的工具,通过这些工具,就算是一个对程序一窍不通的普通网

  • 基于Python实现傻瓜式GIF制作工具

    目录 导语​ ​一.简单的GIF制作 1)准备中 2)小简介 3)代码演示 二.升级imageio的GIF制作 1)准备中 2)小简介 3)代码演示 三.总效果展示 导语​ ​嘿!大家好,我是木木子!今天给大家带来一个好玩儿的Python小程序,希望大家喜欢,记得点点关注啦~ 有没有什么内容形式,比小视频更小,比普通图片更丰富???? 有! GIF动态图就是其中一种形式,而且,必不可少. GIF动态图应该是早已充斥了互联网,被大家玩得不亦乐乎,大伙早就不能接受文章中的纯文字或 静态图片,这些早已

  • 封80端口应对策略 Nginx反向代理For WIN2003超级傻瓜式配置

    Win下最新版本儿:nginx-0.8.31下载地址:http://sysoev.ru/nginx/nginx-0.8.31.zip下载后解压到任意位置,然后修改里面的nginx.conf和添加proxy.conf后启动Nginx.exe即可! proxy.conf内容: 复制代码 代码如下: proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_he

  • webstorm建立vue-cli脚手架的傻瓜式教程

    webstorm 文件 -> new -> project 1.尽量不要勾选 Use the default project setup (babel, eslint) 默认建立的项目好多的配置不全,后期再装很麻烦. 2.点击 create 后出现以下选项,按键盘上下键选择默认(default)还是手动(Manually),如果选择default,一路回车执行下去就行了(注:现在vue-cli3.0默认使用yarn下载),这里我选择手动 3.选择配置,这时你会看见一些选项,你要集成什么就选就行

  • IntelliJ IDEA下的SVN使用(傻瓜式教学)

    第一步:下载svn的客户端,通俗一点来说就是小乌龟啦!去电脑管理的软件管理里面可以直接下载,方便迅速 下载之后直接安装就好了,但是要注意这里的这个文件也要安装上,默认是不安装的,如果不安装,svn中的bin目录下就会没有svn.exe,这个待会会用到,所以一点要注意哦.(都是坑啊) 然后就下一步下一步就安装好了. 第二步:如果已经搭建好了svn服务的话,就要开始在idea中配置相关的配置了,如下图: 看红圈内的就是安装svn(小乌龟)bin目录下的svn.exe 然后点击apply和OK就可以了

随机推荐