傻瓜式解读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 行背景颜色的交替显示(隔行变色)实现代码
Dec 13 Javascript
将函数的实际参数转换成数组的方法
Jan 25 Javascript
js通过更改按钮的显示样式实现按钮的滑动效果
Apr 23 Javascript
JavaScript弹出新窗口后向父窗口输出内容的方法
Apr 06 Javascript
深入理解JavaScript中Ajax
Aug 02 Javascript
深入理解选择框脚本[推荐]
Dec 13 Javascript
layui文件上传实现代码
May 20 Javascript
Vue实现路由跳转和嵌套
Jun 20 Javascript
详解vue-cli 3.0 build包太大导致首屏过长的解决方案
Nov 10 Javascript
用node开发并发布一个cli工具的方法步骤
Jan 03 Javascript
vue实现一拉到底的滑动验证
Jul 25 Javascript
React更新渲染原理深入分析
Dec 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
用DBSQL类加快开发MySQL数据库程序的速度
2006/10/09 PHP
PHP中用接口、抽象类、普通基类实现“面向接口编程”与“耦合方法”简述
2011/03/23 PHP
PHP中spl_autoload_register()和__autoload()区别分析
2014/05/10 PHP
php基于curl扩展制作跨平台的restfule 接口
2015/05/11 PHP
JS获取并操作iframe中元素的方法
2013/03/21 Javascript
jquery动态改变form属性提交表单
2014/06/03 Javascript
15个jquery常用方法、小技巧分享
2015/01/13 Javascript
推荐10 款 SVG 动画的 JavaScript 库
2015/03/24 Javascript
浅谈jQuery.easyui的datebox格式化时间
2015/06/25 Javascript
jQuery实现固定在网页顶部的菜单效果代码
2015/09/02 Javascript
JS实现仿腾讯微博无刷新删除微博效果代码
2015/10/16 Javascript
JavaScript学习笔记之数组随机排序
2016/03/23 Javascript
Jquery操作cookie记住用户名
2016/03/29 Javascript
jquery-mobile表单的创建方法详解
2016/11/23 Javascript
详解Node中导入模块require和import的区别
2017/08/11 Javascript
详解如何使用webpack+es6开发angular1.x
2017/08/16 Javascript
基于JS实现table导出Excel并保留样式
2020/05/19 Javascript
如何利用javascript接收json信息并进行处理
2020/08/06 Javascript
针对Vue路由history模式下Nginx后台配置操作
2020/10/22 Javascript
[11:01]2014DOTA2西雅图邀请赛 冷冷带你探秘威斯汀
2014/07/08 DOTA
Python网络爬虫项目:内容提取器的定义
2016/10/25 Python
python XlsxWriter模块创建aexcel表格的实例讲解
2018/05/03 Python
Python实现多级目录压缩与解压文件的方法
2018/09/01 Python
详解Python 正则表达式模块
2018/11/05 Python
Pytorch十九种损失函数的使用详解
2020/04/29 Python
详解pandas赋值失败问题解决
2020/11/29 Python
网页布局中CSS样式无效的十个重要原因详解
2017/08/10 HTML / CSS
《跨越海峡的生命桥》教学反思
2014/02/24 职场文书
标准化管理实施方案
2014/02/25 职场文书
学习决心书范文
2014/03/11 职场文书
师德师风自查总结
2014/10/14 职场文书
道歉的话怎么说
2015/05/12 职场文书
工作年限证明模板
2015/06/15 职场文书
初中地理教学反思
2016/02/19 职场文书
Redis集群的关闭与重启操作
2021/07/07 Redis
Go gRPC进阶教程gRPC转换HTTP
2022/06/16 Golang