koa-compose源码阅读与学习
源码仓库:koa-compose
前言
文章开始之前来做一道题目。给一个函数数组,封装一个函数可以依次执行这个函数数组里的函数
function func1() {
console.log(1)
}
function func2() {
console.log(2)
}
function func3() {
console.log(3)
}
const arr=[func1,func2,func3]
写一个compose函数,当我们调用compose的时候,依次执行func1、func2、func3,打印出1,2,3,4
function compose(){
//your code goes here...
}
我们很快就能想到使用循环遍历数据,依次执行
function compose() {
for (let item of arr) {
item()
}
}
compose()
//打印输出:
//1
//2
//3
当然这不是我们想要的答案,我们想要函数这样执行func3(func2(func1()))。可以这样写代码
//法一:使用reduce,代码简洁
function compose1() {
return arr.reduce((prev, curr) => (...args) => curr(prev(...args)))
}
// 法二:也可以使用循环遍历赋值
function compose2(){
let prev
for(let i=0;i<arr.length;i++){
prev=arr[i](prev)
}
return prev || function(){}
}
我们变化一下,给函数传入参数,题目变成下面这样
function func1(next){
console.log(1)
next()
console.log(2)
}
function func2(next){
console.log(3)
next()
console.log(4)
}
function func3(next){
console.log(5)
next()
console.log(6)
}
const arr=[func1,func2,func3]
写一个compose函数,当我们调用composeSync的时候,打印出1,3,5,6,4,2
function composeSync(){
//your code goes here...
}
我们先来分析一下题目,每个函数都带有next参数,并且next是一个函数,又因为打印输出的顺序可知,next是数组下一个项。也就是说compose函数需要把arr数组里的每一项都串联起来并把后一项当作参数传入当前项执行,所以前半部分会输出1,3,5.又因为都是同步的代码,所以next()都执行完之后才会执行后面的代码所以输出6,4,2。分析完了之后我们可以开始写代码了,最容易让人想到的方式是递归
//方法一:使用递归
const composeSync1=function(){
function dispatch(index){
if(index===arr.length) return ;
return arr[index](()=>dispatch(index+1))
}
return dispatch(0)
}
//方法二:使用循环(一般能用递归的都能使用循环)
const composeSync2=function(){
let prev=()=>{ }
for(let i=arr.length-1;i>=0;i--){
prev=arr[i].bind(this,prev)
}
return prev()
}
composeSync1()
composeSync2()
//打印输出:
//1
//3
//5
//6
//4
//2
不知不觉我们已经把洋葱模型基本实现了,只要稍加完善(容错处理、异步处理等等)即可使用。下一步我们加上错误处理和异步处理,这个可以参考源码
//容错:判断一下arr是否是数组,判断arr每一项是否是函数,判断数组长度是否大于0,try...catch()...下衔接项
//异步:async...await
koa-compose解析
我们先来字面上理解一下,compose是组合组成的意思,它的作用正是实现洋葱模型,管理所有中间件的。koa-compose是koa的一个中间件,它主要是实现洋葱模型。通过上面的几道题目,可以模糊认为compose就是洋葱模型的雏型,数组里面的每个函数就是一个中间件,洋葱模型的执行机制以及中间件的管理方式。那什么是洋葱模型呢?什么是中间件呢?
洋葱模型与中间件
- 洋葱模型:对数据进行串行处理的一种机制,类似于洋葱,被一层层中间件(处理数据的)包裹。
- 中间件:处理数据的函数、类、方法,分散在模型的各个部位。
盗图两张:
源码解析
'use strict'
/**
* Expose compositor.
*/
module.exports = compose
/**
* Compose `middleware` returning
* a fully valid middleware comprised
* of all those which are passed.
*
* @param {Array} middleware
* @return {Function}
* @api public
*/
function compose (middleware) {
if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
for (const fn of middleware) {
if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
}
/**
* @param {Object} context
* @return {Promise}
* @api public
*/
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)
}
}
}
}
我们先忽略注释,实际代码20行不到,非常精简。首先判断一下传进来的middleware是不是数组并循环一下判断每一项是不是函数。然后在return一个函数传进来参数context(上下文对象)、next(下一步要执行的函数,也就是中间件middleware相比当前项的下一项)。定义dispatch函数用于递归将func1,func2,func3封装成func3(func2(func1))这种结构。首先要判断边界,通过index和middleware长度进行比较,还定义了一个index,用于判断当前的中间件是否已经有过。最后return当前项,把下一项当作参数传给当前项,这样就能保证所有中间件都能嵌套完成。
手动实现一个洋葱模型
实现步骤与思路就是我们刚开始做的那几道题目,一步一步做过来即可实现一个简易版本的洋葱模型。最后贴一下代码
const app = { middlewares: [] };
app.use = (fn) => {
app.middlewares.push(fn);
};
app.compose = function() {
// Your code goes here
function dispatch(index){
if(index===app.middlewares.length) return ;
const fn=app.middlewares[index];
return fn(()=>dispatch(index+1))
}
dispatch(0);
}
app.use(next => {
console.log(1);
next();
console.log(2);
});
app.use(next => {
console.log(3);
next();
console.log(4);
});
app.use(next => {
console.log(5);
next();
console.log(6);
});
app.compose();
参考
- Whats the point of composing middleware in Koa?
- [Source Code Learning--koa] Source Code Interpretation Analysis of Koa Middleware Core (koa-compose)
- Koa引用库之Koa-compose
- KOA2 compose 串联中间件实现(洋葱模型)
- koa-analysis
- call、apply和bind方法的用法以及区别
原文地址:https://www.cnblogs.com/xingguozhiming/p/13286859.html
- 1624: [Usaco2008 Open] Clear And Present Danger 寻宝之路
- 点双连通分量与割点
- 1648: [Usaco2006 Dec]Cow Picnic 奶牛野餐
- 1641: [Usaco2007 Nov]Cow Hurdles 奶牛跨栏
- 1668: [Usaco2006 Oct]Cow Pie Treasures 馅饼里的财富
- 2463: [中山市选2009]谁能赢呢?
- 2748: [HAOI2012]音量调节
- 2697: 特技飞行
- 我这么玩Web Api(二)
- 1296: [SCOI2009]粉刷匠
- 1293: [SCOI2009]生日礼物
- 记一次线程池调优经历
- JavaScript对象
- 1088: [SCOI2005]扫雷Mine
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 自动化运维工具Ansible之LNMP实践环境部署
- 如何在Windows下使用NCL
- Python基础绘图教程(一)
- 如何通过VMware安装Linux CentOS 7.7系统
- HBase入门
- VMware如何克隆一个虚拟机 主机名修改IP地址修改数据目录创建
- Python基础绘图教程(二)
- Docker简介与安装
- python绘图 | 国家气象局开源预报检验库(多图预警)
- Docker镜像与容器的常用操作
- DiDi Kafka-Manager安装和简单使用
- Docker数据管理与挂载管理
- Docker Dockerfile 指令详解与实战案例
- flume kafka和sparkstreaming整合
- Docker如何搭建私有registry镜像仓库