傻瓜式解读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 相关文章推荐
[原创]来自ImageSee官方 JavaScript图片浏览器
Jan 16 Javascript
判断JavaScript对象是否可用的最正确方法分析
Oct 03 Javascript
javascript XML数据显示为HTML一例
Dec 23 Javascript
jquery 关于event.target使用的几点说明介绍
Apr 26 Javascript
js 自定义个性下拉选择框示例
Aug 20 Javascript
JavaScript判断一个字符串是否包含指定子字符串的方法
Mar 18 Javascript
js操作数组函数实例小结
Dec 10 Javascript
jQuery Form表单取值的方法
Jan 11 Javascript
关于vue单文件中引用路径的处理方法
Jan 08 Javascript
vue鼠标移入添加class样式,鼠标移出去除样式(active)实现方法
Aug 22 Javascript
node.js中express模块创建服务器和http模块客户端发请求
Mar 06 Javascript
vue video和vue-video-player实现视频铺满教程
Oct 30 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仿微信红包分配算法的实现方法
2016/05/13 PHP
PHP中抽象类,接口功能、定义方法示例
2019/02/26 PHP
比Jquery的document.ready更快的方法
2010/04/28 Javascript
JavaScript面向对象程序设计三 原型模式(上)
2011/12/21 Javascript
javascript中注册和移除事件的4种方式
2013/03/20 Javascript
jquery实现图片渐变切换兼容ie6/Chrome/Firefox
2013/08/02 Javascript
JavaScript对HTML DOM使用EventListener进行操作
2015/10/21 Javascript
JavaScript驾驭网页-获取网页元素
2016/03/24 Javascript
JQuery PHP图片在线裁剪实例
2020/07/27 Javascript
浅谈ES6 模板字符串的具体使用方法
2017/11/07 Javascript
JavaScript基于面向对象实现的猜拳游戏
2018/01/03 Javascript
Vue2.0 实现歌手列表滚动及右侧快速入口功能
2018/08/08 Javascript
微信小程序webview实现长按点击识别二维码功能示例
2019/01/24 Javascript
express.js中间件说明详解
2019/03/19 Javascript
详解Vue源码中一些util函数
2019/04/24 Javascript
jquery+ajax实现上传图片并显示上传进度功能【附php后台接收】
2019/06/06 jQuery
[01:55]《走出家门看比赛》——DOTA2 2015国际邀请赛同城线下观战
2015/07/18 DOTA
[46:53]Secret vs Liquid 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/17 DOTA
videocapture库制作python视频高速传输程序
2013/12/23 Python
python模拟登陆Tom邮箱示例分享
2014/01/13 Python
Python pickle类库介绍(对象序列化和反序列化)
2014/11/21 Python
用python简单实现mysql数据同步到ElasticSearch的教程
2018/05/30 Python
对python pandas读取剪贴板内容的方法详解
2019/01/24 Python
Python3.4学习笔记之 idle 清屏扩展插件用法分析
2019/03/01 Python
Python安装tar.gz格式文件方法详解
2020/01/19 Python
Python通过format函数格式化显示值
2020/10/17 Python
一款纯css3实现简单的checkbox复选框和radio单选框
2014/11/05 HTML / CSS
小学后勤管理制度
2014/01/14 职场文书
护士节演讲稿开场白
2014/08/25 职场文书
公务员年度考核登记表个人总结
2015/02/12 职场文书
市场部岗位职责
2015/02/12 职场文书
2020年基层司法所建设情况调研报告
2019/11/30 职场文书
python基础之匿名函数详解
2021/04/21 Python
详解thinkphp的Auth类认证
2021/05/28 PHP
SQL实现LeetCode(180.连续的数字)
2021/08/04 MySQL
JavaScript小技巧带你提升你的代码技能
2021/09/15 Javascript