傻瓜式解读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 相关文章推荐
jquery插件jquery.beforeafter.js实现左右拖拽分隔条对比图片的方法
Aug 07 Javascript
Js删除数组中某一项或几项的几种方法(推荐)
Jul 27 Javascript
Vue.js创建Calendar日历效果
Nov 03 Javascript
Chrome浏览器的alert弹窗禁止再次弹出后恢复的方法
Dec 30 Javascript
详解使用vue实现tab 切换操作
Jul 03 Javascript
webpack2.0配置postcss-loader的方法
Aug 17 Javascript
Angular客户端请求Rest服务跨域问题的解决方法
Sep 19 Javascript
微信小程序实现图片放大预览功能
Oct 22 Javascript
[原创]js实现保存文本框内容为本地文件兼容IE,chrome,火狐浏览器
Feb 14 Javascript
解决vue-cli项目打包出现空白页和路径错误的问题
Sep 04 Javascript
Vue从TodoList中学父子组件通信
Feb 05 Javascript
对layui中table组件工具栏的使用详解
Sep 19 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
自己做矿石收音机
2021/03/02 无线电
PHP 日期时间函数的高级应用技巧
2009/10/10 PHP
让的PHP代码飞起来的40条小技巧(提升php效率)
2010/04/12 PHP
PHP 八种基本的数据类型小结
2011/06/01 PHP
PHP批量采集下载美女图片的实现代码
2013/06/03 PHP
通过php删除xml文档内容的方法
2015/01/23 PHP
PHP多维数组元素操作类的方法
2016/11/14 PHP
php类自动装载、链式操作、魔术方法实现代码
2017/07/23 PHP
javascript面向对象包装类Class封装类库剖析
2013/01/24 Javascript
js nextSibling属性和previousSibling属性概述及使用注意
2013/02/16 Javascript
JavaScript操纵窗口的方法小结
2013/06/28 Javascript
为开发者准备的10款最好的jQuery日历插件
2014/02/04 Javascript
2014 年最热门的21款JavaScript框架推荐
2014/12/25 Javascript
小米公司JavaScript面试题
2014/12/29 Javascript
javascript中的正则表达式使用详解
2015/08/30 Javascript
使用travis-ci如何持续部署node.js应用详解
2017/07/30 Javascript
探究react-native 源码的图片缓存问题
2017/08/24 Javascript
详解jquery插件jquery.viewport.js学习使用方法
2017/09/08 jQuery
JavaScript 异步调用
2017/10/25 Javascript
js实现电灯开关效果
2021/01/19 Javascript
[03:07]【DOTA2亚洲邀请赛】我们,梦开始的地方
2017/03/07 DOTA
python django事务transaction源码分析详解
2017/03/17 Python
Sublime开发python程序的示例代码
2018/01/24 Python
python 自动轨迹绘制的实例代码
2019/07/05 Python
Pytorch: 自定义网络层实例
2020/01/07 Python
git查看、创建、删除、本地、远程分支方法详解
2020/02/18 Python
如何利用Python 进行边缘检测
2020/10/14 Python
德国低价购买灯具和家具网站:Style-home.de
2016/11/25 全球购物
全球速卖通:AliExpress(国际版淘宝)
2017/09/20 全球购物
C#公司笔试题
2014/03/28 面试题
Linux常见面试题
2016/10/04 面试题
2014和解协议书范文
2014/09/15 职场文书
街道务虚会发言材料
2014/10/20 职场文书
幼儿园小班班务总结
2015/08/03 职场文书
用position:sticky完美解决小程序吸顶问题的实现方法
2021/04/24 HTML / CSS
《帝国时代4》赛季预告 新增内容编译器可创造地图
2022/04/03 其他游戏