傻瓜式解读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控制表单不能输入空格的小例子
Nov 20 Javascript
jQuery 更改checkbox的状态,无效的解决方法
Jul 22 Javascript
JavaScript中const、var和let区别浅析
Oct 11 Javascript
使用openSpeDiv方法实现Ecshop登录弹窗框效果
Mar 13 Javascript
详解在Angularjs中ui-sref和$state.go如何传递参数
Apr 24 Javascript
详解Vue学习笔记进阶篇之列表过渡及其他
Jul 17 Javascript
Javascript实现异步编程的过程
Jun 18 Javascript
AngularJs分页插件使用详解
Jun 30 Javascript
详解vue 图片上传功能
Apr 30 Javascript
jquery.pager.js分页实现详解
Jul 29 jQuery
JS原型对象操作实例分析
Jun 06 Javascript
详解Howler.js Web音频播放终极解决方案
Aug 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 抓取网页图片并且另存为的实现代码
2010/03/24 PHP
The specified CGI application misbehaved by not returning a complete set of HTTP headers
2011/03/31 PHP
微博短链接算法php版本实现代码
2012/09/15 PHP
完美的2个php检测字符串是否是utf-8编码函数分享
2014/07/28 PHP
PHP如何通过AJAX方式实现登录功能
2015/11/23 PHP
joomla组件开发入门教程
2016/05/04 PHP
PHP实现模拟http请求的方法分析
2017/12/20 PHP
javascript字典探测用户名工具
2006/10/05 Javascript
jquery 倒计时效果实现秒杀思路
2013/09/11 Javascript
javascript列表框操作函数集合汇总
2013/11/28 Javascript
javascript从image转换为base64位编码的String
2014/07/29 Javascript
使表格的标题列可左右拉伸jquery插件封装
2014/11/24 Javascript
url中的特殊符号有什么含义(推荐)
2016/06/17 Javascript
利用jQuery异步上传文件的插件用法详解
2017/07/19 jQuery
iframe高度自适应及隐藏滚动条的实例详解
2017/09/29 Javascript
微信小程序使用input组件实现密码框功能【附源码下载】
2017/12/11 Javascript
使用JS模拟锚点跳转的实例
2018/02/01 Javascript
jQuery使用bind动态绑定事件无效的处理方法
2018/12/11 jQuery
Vue起步(无cli)的啊教程详解
2019/04/11 Javascript
js模拟F11页面全屏显示
2019/09/17 Javascript
python的绘图工具matplotlib使用实例
2014/07/03 Python
Python实现的数据结构与算法之双端队列详解
2015/04/22 Python
python中的变量如何开辟内存
2018/06/26 Python
详解Python3中的迭代器和生成器及其区别
2018/10/09 Python
如何在Django中添加没有微秒的 DateTimeField 属性详解
2019/01/30 Python
wxPython实现整点报时
2019/11/18 Python
Pycharm github配置实现过程图解
2020/10/13 Python
Python爬虫之Selenium库的使用方法
2021/01/03 Python
viagogo法国票务平台:演唱会、体育比赛、戏剧门票
2017/03/27 全球购物
英国发展最快的在线超市之一:Click Marketplace
2021/02/15 全球购物
平安建设实施方案
2014/03/19 职场文书
毕业班联欢会主持词
2014/03/27 职场文书
2014年依法行政工作总结
2014/11/19 职场文书
幼儿教师个人总结
2015/02/05 职场文书
2015年大学班级工作总结
2015/04/28 职场文书
2019年新郎保证书3篇
2019/10/17 职场文书