傻瓜式解读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下给元素添加事件的方法与代码
Aug 13 Javascript
javascript实现二分查找法实现代码
Nov 12 Javascript
某人初学javascript的时候写的学习笔记
Dec 30 Javascript
extjs tabpanel限制选项卡数量实现思路及代码
Apr 02 Javascript
jquery mobile页面跳转后样式丢失js失效的解决方法
Sep 06 Javascript
JavaScript编程的单例设计模讲解
Nov 10 Javascript
再次谈论React.js实现原生js拖拽效果引起的一系列问题
Apr 03 Javascript
jQuery中text() val()和html()的区别实例详解
Jun 28 Javascript
JS获取子节点、父节点和兄弟节点的方法实例总结
Jul 06 Javascript
详解项目升级到vue-cli3的正确姿势
Jan 28 Javascript
微信小程序实现多选框全选与取消全选功能示例
May 14 Javascript
JavaScript 防篡改对象的用法示例
Apr 24 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
《心理测量者3》剧场版动画预告
2020/03/02 日漫
php相当简单的分页类
2008/10/02 PHP
一步一步学习PHP(2)――PHP类型
2010/02/15 PHP
强制PHP命令行脚本单进程运行的方法
2014/04/15 PHP
kohana框架上传文件验证规则写法示例
2014/07/14 PHP
Yii实现MySQL多数据库和读写分离实例分析
2014/12/03 PHP
在 Laravel 中动态隐藏 API 字段的方法
2019/10/25 PHP
javascript下判断一个对象是否具有指定名称的属性的的代码
2010/01/11 Javascript
JQuery UI的拖拽功能实现方法小结
2012/03/14 Javascript
Node.js中调用mysql存储过程示例
2014/12/20 Javascript
jquery 插件实现多行文本框[textarea]自动高度
2015/03/04 Javascript
JS制作简单的三级联动
2015/03/18 Javascript
js提示框替代系统alert,自动关闭alert对话框的实现方法
2016/11/07 Javascript
Vue 表单控件绑定的实现示例
2017/08/11 Javascript
JS实现中英文混合文字溢出友好截取功能
2018/08/06 Javascript
python requests 库请求带有文件参数的接口实例
2019/01/03 Python
Python交互式图形编程的实现
2019/07/25 Python
Jupyter Notebook远程登录及密码设置操作
2020/04/10 Python
css3的@media属性实现页面响应式布局示例代码
2014/02/10 HTML / CSS
浅析CSS3 用text-overflow解决文字排版问题
2020/10/28 HTML / CSS
英国最出名高街品牌:Forever Unique
2018/02/24 全球购物
年终自我鉴定
2013/10/09 职场文书
国际商务系学生个人的自我评价
2013/11/26 职场文书
会计出纳员的自我评价
2014/01/15 职场文书
时尚休闲吧创业计划书
2014/01/25 职场文书
人事部专员岗位职责
2014/03/04 职场文书
政治学求职信
2014/06/03 职场文书
我的梦想演讲稿500字
2014/08/21 职场文书
中学教师师德师风演讲稿
2014/08/22 职场文书
最美护士演讲稿
2014/08/27 职场文书
咖啡厅商业计划书
2014/09/15 职场文书
女方离婚起诉书
2015/05/18 职场文书
2015年教研员工作总结
2015/05/26 职场文书
python tkinter实现定时关机
2021/04/21 Python
一篇文章学会Vue中间件管道
2021/06/20 Vue.js
实现GO语言对数组切片去重
2022/04/20 Golang