傻瓜式解读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动态操作table(新增,删除相关列信息)
May 23 Javascript
js识别不同浏览器基于userAgent做判断
Jul 29 Javascript
JavaScript实现同步于本地时间的动态时间显示方法
Feb 02 Javascript
最精简的JavaScript实现鼠标拖动效果的方法
May 11 Javascript
jquery实现仿JqueryUi可拖动的DIV实例
Jul 31 Javascript
全面解析JavaScript里的循环方法之forEach,for-in,for-of
Apr 20 Javascript
AngularJS 表达式详细讲解及实例代码
Jul 26 Javascript
vue脚手架vue-cli的学习使用教程
Jun 06 Javascript
jQuery 实现倒计时天,时,分,秒功能
Jul 31 jQuery
原生JS实现简单的无缝自动轮播效果
Sep 26 Javascript
js实现一个页面多个倒计时的3种方法
Feb 25 Javascript
vue 如何从单页应用改造成多页应用
Oct 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 图片加水印与上传图片加水印php类
2010/05/12 PHP
PHP判断文件是否被引入的方法get_included_files用法示例
2016/11/29 PHP
使用jQuery向asp.net Mvc传递复杂json数据-ModelBinder篇
2010/05/07 Javascript
读jQuery之七 判断点击了鼠标哪个键的代码
2011/06/21 Javascript
js中split和replace的用法实例
2015/02/28 Javascript
使用AngularJS实现表单向导的方法
2015/06/19 Javascript
Ionic如何实现下拉刷新与上拉加载功能
2016/06/03 Javascript
jQuery子元素过滤选择器用法示例
2016/09/09 Javascript
关于json字符串与实体之间的严格验证代码
2016/11/10 Javascript
jquery实现下拉框左右选择功能
2017/02/21 Javascript
js实现图片懒加载效果
2017/07/17 Javascript
浅谈在koa2中实现页面渲染的全局数据
2017/10/09 Javascript
详解vue-router 命名路由和命名视图
2018/06/01 Javascript
小程序实现左滑删除功能
2018/10/30 Javascript
extract-text-webpack-plugin用法详解
2019/02/14 Javascript
NodeJS读取分析Nginx错误日志的方法
2019/05/14 NodeJs
vue中nextTick用法实例
2019/09/11 Javascript
Vue利用Blob下载原生二进制数组文件
2019/09/25 Javascript
简单了解Vue computed属性及watch区别
2020/07/10 Javascript
Nuxt pages下不同的页面对应layout下的页面布局操作
2020/11/05 Javascript
jQuery实现tab栏切换效果
2020/12/22 jQuery
[01:01:52]DOTA2-DPC中国联赛正赛 iG vs LBZS BO3 第一场 3月4日
2021/03/11 DOTA
【Python】Python的urllib模块、urllib2模块批量进行网页下载文件
2016/11/19 Python
python绘制铅球的运行轨迹代码分享
2017/11/14 Python
Python推导式简单示例【列表推导式、字典推导式与集合推导式】
2018/12/04 Python
python Elasticsearch索引建立和数据的上传详解
2019/08/04 Python
python os模块在系统管理中的应用
2020/06/22 Python
html5+css如何实现中间大两头小的轮播效果
2018/12/06 HTML / CSS
使用html5 canvas绘制圆环动效
2019/06/03 HTML / CSS
TheFork葡萄牙:欧洲领先的在线餐厅预订平台
2019/05/27 全球购物
瑞士图书网站:Weltbild.ch
2019/09/17 全球购物
有兼职工作经历的简历自我评价
2014/03/07 职场文书
党员群众路线对照检查材料
2014/08/31 职场文书
关于清明节的演讲稿
2014/09/13 职场文书
2015年林业工作总结
2015/05/14 职场文书
2015年生产部工作总结范文
2015/05/25 职场文书