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


Posted in Javascript onOctober 30, 2018

最近需要单独使用到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(),按照上面的套路走,相信很快就明白了。

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

Javascript 相关文章推荐
Firefox和IE兼容性问题及解决方法总结
Oct 08 Javascript
jquery ui bootstrap 实现自定义风格
Nov 14 Javascript
基于JavaScript实现快速转换文本语言(繁体中文和简体中文)
Mar 07 Javascript
AngularJS在IE下取数据总是缓存问题的解决方法
Aug 05 Javascript
JS获取鼠标选中的文字
Aug 10 Javascript
js仿腾讯QQ的web登陆界面
Aug 19 Javascript
解析JavaScript数组方法reduce
Dec 12 Javascript
使用JS组件实现带ToolTip验证框的实例代码
Aug 23 Javascript
详解vue-router 命名路由和命名视图
Jun 01 Javascript
Node.js在图片模板上生成二维码图片并附带底部文字说明实现详解
Aug 07 Javascript
将Vue组件库更换为按需加载的方法步骤
May 06 Javascript
vuejs实现下拉框菜单选择
Oct 23 Javascript
微信小程序实现单选功能
Oct 30 #Javascript
基于vue2.0实现仿百度前端分页效果附实现代码
Oct 30 #Javascript
小程序实现多选框功能
Oct 30 #Javascript
vue-cli项目配置多环境的详细操作过程
Oct 30 #Javascript
详解微信小程序中组件通讯
Oct 30 #Javascript
vue移动端项目缓存问题实践记录
Oct 29 #Javascript
vue 使用vue-i18n做全局中英文切换的方法
Oct 29 #Javascript
You might like
php zlib压缩和解压缩swf文件的代码
2008/12/30 PHP
浅析php中json_encode()和json_decode()
2014/05/25 PHP
PHP中模拟处理HTTP PUT请求的例子
2014/07/22 PHP
PHP中strtr字符串替换用法详解
2014/11/26 PHP
PHP SPL标准库之文件操作(SplFileInfo和SplFileObject)实例
2015/05/11 PHP
php分割合并两个字符串的函数实例
2015/06/19 PHP
thinkphp3.x连接mysql数据库的方法(具体操作步骤)
2016/05/19 PHP
php获取开始与结束日期之间所有日期的方法
2016/11/29 PHP
使用Codeigniter重写insert的方法(推荐)
2017/03/23 PHP
PHP实现十进制数字与二十六进制字母串相互转换操作示例
2018/08/10 PHP
PHP内部实现打乱字符串顺序函数str_shuffle的方法
2019/02/14 PHP
利用PHP内置SERVER开启web服务(本地开发使用)
2021/03/09 PHP
capacityFixed 基于jquery的类似于新浪微博新消息提示的定位框
2011/05/24 Javascript
图片img的src不变让浏览器重新加载实现方法
2013/03/29 Javascript
探讨js字符串数组拼接的性能问题
2014/10/11 Javascript
JS判断客服QQ号在线还是离线状态的方法
2015/01/13 Javascript
Javascript基础教程之break和continue语句
2015/01/18 Javascript
详解Angularjs filter过滤器
2016/02/06 Javascript
ES6数组的扩展详解
2017/04/25 Javascript
JS脚本加载后执行相应回调函数的操作方法
2018/02/28 Javascript
原生JS+HTML5实现的可调节写字板功能示例
2018/08/30 Javascript
nodejs基础之buffer缓冲区用法分析
2018/12/26 NodeJs
使用Angular Cli如何创建Angular私有库详解
2019/01/30 Javascript
基于jQuery实现可编辑的表格
2019/12/11 jQuery
手动实现vue2.0的双向数据绑定原理详解
2021/02/06 Vue.js
[01:46]辉夜杯—打造中国DOTA新格局
2015/12/25 DOTA
python3.6环境安装+pip环境配置教程图文详解
2019/06/20 Python
python scrapy爬虫代码及填坑
2019/08/12 Python
日本钓鱼渔具和户外用品网上商店:naturum
2016/08/07 全球购物
Bose法国官网:购买耳机、扬声器、家庭影院、专业音响
2017/12/21 全球购物
彪马法国官网:PUMA法国
2019/12/15 全球购物
大学生职业生涯规划书范文
2014/01/04 职场文书
学雷锋月活动总结
2014/04/25 职场文书
人事主管岗位职责说明书
2014/07/30 职场文书
施工员岗位职责
2015/02/10 职场文书
小学生暑假安全公约
2015/07/14 职场文书