傻瓜式解读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 相关文章推荐
使用Javascript接收get传递的值的代码
Nov 30 Javascript
jquery中通过父级查找进行定位示例
Jun 28 Javascript
解决html按钮切换绑定不同函数后点击时执行多次函数问题
May 14 Javascript
一个字符串反转函数可实现字符串倒序
Sep 15 Javascript
用队列模拟jquery的动画算法实例
Jan 20 Javascript
JavaScript判断undefined类型的正确方法
Jun 30 Javascript
JS获取文件大小方法小结
Dec 08 Javascript
JS高阶函数原理与用法实例分析
Jan 15 Javascript
Promise扫盲贴
Jun 24 Javascript
ES6 Array常用扩展的应用实例分析
Jun 26 Javascript
JavaScript函数重载操作实例浅析
May 02 Javascript
JavaScript实现刮刮乐效果
Nov 01 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 header()函数使用说明
2008/07/10 PHP
PHP学习笔记(二) 了解PHP的基本语法以及目录结构
2014/08/04 PHP
javascript 写类方式之五
2009/07/05 Javascript
jQuery 创建Dom元素
2010/05/07 Javascript
JS实现点击下载的小例子
2013/07/10 Javascript
Nodejs实现的一个简单udp广播服务器、客户端
2014/09/25 NodeJs
js格式化时间小结
2014/11/03 Javascript
JS设置网页图片vspace和hspace属性的方法
2015/04/01 Javascript
js实现超简单的展开、折叠目录代码
2015/08/28 Javascript
JQuery日期插件datepicker的使用方法
2016/03/03 Javascript
解决微信浏览器Javascript无法使用window.location.reload()刷新页面
2016/06/21 Javascript
EasyUI创建对话框的两种方式
2016/08/23 Javascript
移动端基础事件总结与应用
2017/01/12 Javascript
提升页面加载速度的插件InstantClick
2017/09/12 Javascript
vue登录路由验证的实现
2017/12/13 Javascript
利用Vue2.x开发实现JSON树的方法
2018/01/04 Javascript
vue 实现的树形菜的实例代码
2018/03/19 Javascript
不得不知的ES6小技巧
2018/07/28 Javascript
解决vue cli使用typescript后打包巨慢的问题
2019/09/30 Javascript
vue中实现图片压缩 file文件的方法
2020/05/28 Javascript
django开发教程之利用缓存文件进行页面缓存的方法
2017/11/10 Python
matplotlib 输出保存指定尺寸的图片方法
2018/05/24 Python
解决python读取几千万行的大表内存问题
2018/06/26 Python
mac下如何将python2.7改为python3
2018/07/13 Python
python爬虫 execjs安装配置及使用
2019/07/30 Python
pytest中文文档之编写断言
2019/09/12 Python
Python中低维数组填充高维数组的实现
2019/12/02 Python
微软瑞士官方网站:Microsoft瑞士
2018/04/20 全球购物
美国迪克体育用品商店:DICK’S Sporting Goods
2018/07/24 全球购物
高中生学习生活的自我评价
2013/11/27 职场文书
医药代表个人求职信范本
2013/12/19 职场文书
2014年建筑工作总结
2014/11/26 职场文书
致短跑运动员加油稿
2015/07/21 职场文书
小学一年级语文教学反思
2016/03/03 职场文书
2019年大学毕业生个人自我鉴定范文大全
2019/03/21 职场文书
2019奶茶店创业计划书范本!
2019/07/15 职场文书