傻瓜式解读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 相关文章推荐
JS类定义原型方法的两种实现的区别评论很多
Sep 12 Javascript
javascript检测页面是否缩放的小例子
May 16 Javascript
在JS中操作时间之getUTCMilliseconds()方法的使用
Jun 10 Javascript
javascript学习总结之js使用技巧
Sep 02 Javascript
JavaScript字符串删除重复字符的方法
Dec 25 Javascript
详解webpack 如何集成第三方js库
Jun 29 Javascript
Vuex 使用及简单实例(计数器)
Aug 29 Javascript
Vue-component全局注册实例
Sep 06 Javascript
jQuery实现动态加载(按需加载)javascript文件的方法分析
May 31 jQuery
vue-socket.io跨域问题有效解决方法
Feb 11 Javascript
Vue-router 报错NavigationDuplicated的解决方法
Mar 31 Javascript
js实现双人五子棋小游戏
May 28 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及Zend Engine的线程安全模型分析
2011/11/10 PHP
php有效防止图片盗用、盗链的两种方法
2016/11/01 PHP
把html页面的部分内容保存成新的html文件的jquery代码
2009/11/12 Javascript
使用JQuery进行跨域请求
2010/01/25 Javascript
缓动函数requestAnimationFrame 更好的实现浏览器经动画
2012/12/07 Javascript
jquery中子元素和后代元素的区别示例介绍
2014/04/02 Javascript
Js可拖拽放大的层拖动特效实现方法
2015/02/25 Javascript
jQuery滚动加载图片实现原理
2015/12/14 Javascript
JavaScript中Array的实用操作技巧分享
2016/09/11 Javascript
浅谈JavaScript的函数及作用域
2016/12/30 Javascript
javascript数组拍平方法总结
2018/01/20 Javascript
在angular 6中使用 less 的实例代码
2018/05/13 Javascript
JS实现的汉字与Unicode码相互转化功能分析
2018/05/25 Javascript
如何为vue的项目添加单元测试
2018/12/19 Javascript
vue-cli3访问public文件夹静态资源报错的解决方式
2020/09/02 Javascript
JavaScript实现音乐导航效果
2020/11/19 Javascript
python正则表达式re模块详细介绍
2014/05/29 Python
python中的错误处理
2016/04/10 Python
Python实现二分查找与bisect模块详解
2017/01/13 Python
python 多维切片之冒号和三个点的用法介绍
2018/04/19 Python
Python对象转换为json的方法步骤
2019/04/25 Python
Python3+Appium实现多台移动设备操作的方法
2019/07/05 Python
Django 开发调试工具 Django-debug-toolbar使用详解
2019/07/23 Python
python函数装饰器之带参数的函数和带参数的装饰器用法示例
2019/11/06 Python
python绘制无向图度分布曲线示例
2019/11/22 Python
python isinstance函数用法详解
2020/02/13 Python
全网首秀之Pycharm十大实用技巧(推荐)
2020/04/27 Python
python邮件中附加文字、html、图片、附件实现方法
2021/01/04 Python
HTML页面中添加Canvas标签示例
2015/01/01 HTML / CSS
KIEHL’S科颜氏官方旗舰店:源自美国的顶级护肤品牌
2018/06/07 全球购物
美国在线纱线商店:Darn Good Yarn
2019/03/20 全球购物
DataReader和DataSet的异同
2014/12/31 面试题
安娜卡列尼娜观后感
2015/06/11 职场文书
七年级作文之下雨天
2019/12/23 职场文书
如何通过简单的代码描述Angular父组件、子组件传值
2022/04/07 Javascript
深入理解mysql事务隔离级别和存储引擎
2022/04/12 MySQL