热门标签 | HotTags
当前位置:  开发笔记 > 开发工具 > 正文

深入解析koa之中间件流程控制

这篇文章主要介绍了深入解析koa之中间件流程控制,koa被认为是第二代web后端开发框架,相比于前代express而言,其最大的特色无疑就是解决了回调金字塔的问题,让异步的写法更加的简洁。,需要的朋友可以参考下

前言

koa被认为是第二代web后端开发框架,相比于前代express而言,其最大的特色无疑就是解决了回调金字塔的问题,让异步的写法更加的简洁。在使用koa的过程中,其实一直比较好奇koa内部的实现机理。最近终于有空,比较深入的研究了一下koa一些原理,在这里会写一系列文章来记录一下我的学习心得和理解。

在我看来,koa最核心的函数是大名鼎鼎的co,koa正是基于这个函数实现了异步回调同步化,以及中间件流程控制。当然在这篇文章中我并不会去分析co源码,我打算在整个系列文章中,一步一步讲解如何实现koa中间件的流程控制原理,koa的异步回调同步写法实现原理,最后在理解这些的基础上,实现一个简单的类似co的函数。

本篇首先只谈一谈koa的中间件流程控制原理。

1. koa中间件执行流程

关于koa中间件如何执行,官网上有一个非常经典的例子,有兴趣的可以去看看,不过这里,我想把它修改的更简单一点:

var koa = require('koa');
var app = koa();
app.use(function*(next) {
console.log('begin middleware 1');
yield next;
console.log('end middleware 1');
});
app.use(function*(next) {
console.log('begin middleware 2');
yield next;
console.log('end middleware 2');
});
app.use(function*() {
console.log('middleware 3');
});
app.listen(3000);

运行这个例子,然后使用curl工具,运行:

curl http://localhost:3000

可以看到,运行之后,会输出:

begin middleware 1
begin middleware 2
middleware 3
end middleware 2
end middleware 1

这个例子非常形象的代表了koa的中间件执行机制,可以用下图的洋葱模型来形容:

通过这种执行流程,开发者可以非常方便的开发一些中间件,并且非常容易的整合到实际业务流程中。那么,这样的流程又是如何实现和控制的呢?

2. koa中的generator和compose

简单来说,洋葱模型的执行流程是通过es6中的generator来实现的。不熟悉generator的同学可以去看看其特性,其中一个就是generator函数可以像打断点一样从函数某个地方跳出,之后还可以再回来继续执行。下面一个例子可以说明这种特性:

var gen=function*(){
console.log('begin!');
//yield语句,在这里跳出,将控制权交给anotherfunc函数。
yield anotherfunc;
//下次回来时候从这里开始执行
console.log('end!');
}
var anotherfunc(){
console.log('this is another function!');
}
var g=gen();
var another=g.next(); //'begin!'
//another是一个对象,其中value成员就是返回的anotherfunc函数
another.value(); //'this is another function!'
g.next(); //'end!';

从这个简单例子中,可以看出洋葱模型最基本的一个雏形,即yield前后的语句最先和最后执行,yield中间的代码在中心执行。

现在设想一下,如果yield后面跟的函数本身就又是一个generator,会怎么样呢?其实就是从上面例子里面做一个引申:

var gen1=function*(){
console.log('begin!');
yield g2;
console.log('end!');
}
var gen2=function*(){
console.log('begin 2');
yield anotherfunc;
console.log('end 2');
}
var anotherfunc(){
console.log('this is another function!');
}
var g=gen();
var g2=gen2();
var another1=g.next(); //'begin!';
var another2=another1.value.next(); //'begin 2';
another2.value(); //'this is another function!';
another1.value.next(); //'end 2';
g.next(); //'end!';

可以看出,基本上是用上面的例子,再加一个嵌套而已,原理是一样的。

而在koa中,每个中间件generator都有一个next参数。在我们这个例子中,g2就可以看成是g函数的next参数。事实上,koa也确实是这样做的,当使用app.use()挂载了所有中间件之后,koa有一个koa-compose模块,用于将所有generator中间件串联起来,基本上就是将后一个generator赋给前一个generator的next参数。koa-compose的源码非常简单短小,下面是我自己实现的一个:

function compose(middlewares) {
return function(next) {
var i = middlewares.length;
var next = function*() {}();
while (i--) {
next = middlewares[i].call(this, next);
}
return next;
}
}

使用我们自己写的compose对上面一个例子改造,是的其更接近koa的形式:

function compose(middlewares) {
return function(next) {
var i = middlewares.length;
var next = function*() {}();
while (i--) {
next = middlewares[i].call(this, next);
}
return next;
}
}
var gen1=function*(next){
console.log('begin!');
yield next;
console.log('end!');
}
var gen2=function*(next){
console.log('begin 2');
yield next;
console.log('end 2');
}
var gen3=function*(next){
console.log('this is another function!');
}
var bundle=compose([gen1,gen2,gen3]);
var g=bundle();
var another1=g.next(); //'begin!';
var another2=another1.value.next(); //'begin 2';
another2.value.next(); //'this is another function!';
another1.value.next(); //'end 2';
g.next(); //'end!';

怎么样?是不是有一点koa中间件写法的感觉了呢?但是目前,我们还是一步一步手动的在执行我们这个洋葱模型,能否写一个函数,自动的来执行我们这个模型呢?

3. 让洋葱模型自动跑起来:一个run函数的编写

上面例子中,最后的代码我们可以看出一个规律,基本就是外层的generator调用next方法把控制权交给内层,内层再继续调用next把方法交给更里面的一层。整个流程可以用一个函数嵌套的写法写出来。话不多说,直接上代码:

function run(gen) {
var g;
if (typeof gen.next === 'function') {
g = gen;
} else {
g = gen();
}
function next() {
var tmp = g.next();
//如果tmp.done为true,那么证明generator执行结束,返回。
if (tmp.done) {
return;
} else if (typeof g.next === 'function') {
run(tmp.value);
next();
}
}
next();
}
function compose(middlewares) {
return function(next) {
var i = middlewares.length;
var next = function*() {}();
while (i--) {
next = middlewares[i].call(this, next);
}
return next;
}
}
var gen1 = function*(next) {
console.log('begin!');
yield next;
console.log('end!');
}
var gen2 = function*(next) {
console.log('begin 2');
yield next;
console.log('end 2');
}
var gen3 = function*(next) {
console.log('this is another function!');
}
var bundle = compose([gen1, gen2, gen3]);
run(bundle);

run函数接受一个generator,其内部执行其实就是我们上一个例子的精简,使用递归的方法执行。运行这个例子,可以看到结果和我们上一个例子相同。

到此为止,我们就基本讲清楚了koa中的中间件洋葱模型是如何自动执行的。事实上,koa中使用的co函数,一部分功能就是实现我们这里编写的run函数的功能。

值得注意的是,这篇文章只注重分析中间件执行流程的实现,暂时并没有考虑异步回调同步化原理。下一篇文章中,我将带大家继续探析koa中异步回调同步化写法的机理。

这篇文章的代码可以在github上面找到:https://github.com/mly-zju/async-js-demo,其中process_control.js文件就是本篇的事例源码。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


推荐阅读
  • Composer Registry Manager:PHP的源切换管理工具
    本文介绍了一个用于Composer的源切换管理工具——Composer Registry Manager。该项目旨在简化Composer包源的管理和切换,避免与常见的CRM系统混淆,并提供了详细的安装和使用指南。 ... [详细]
  • 基于KVM的SRIOV直通配置及性能测试
    SRIOV介绍、VF直通配置,以及包转发率性能测试小慢哥的原创文章,欢迎转载目录?1.SRIOV介绍?2.环境说明?3.开启SRIOV?4.生成VF?5.VF ... [详细]
  • 本文详细介绍了Git分布式版本控制系统中远程仓库的概念和操作方法。通过具体案例,帮助读者更好地理解和掌握如何高效管理代码库。 ... [详细]
  • dotnet 通过 Elmish.WPF 使用 F# 编写 WPF 应用
    本文来安利大家一个有趣而且强大的库,通过F#和C#混合编程编写WPF应用,可以在WPF中使用到F#强大的数据处理能力在GitHub上完全开源Elmis ... [详细]
  • 探讨了小型企业在构建安全网络和软件时所面临的挑战和机遇。本文介绍了如何通过合理的方法和工具,确保小型企业能够有效提升其软件的安全性,从而保护客户数据并增强市场竞争力。 ... [详细]
  • 本文详细介绍了如何在 Windows 环境下使用 node-gyp 工具进行 Node.js 本地扩展的编译和配置,涵盖从环境搭建到代码实现的全过程。 ... [详细]
  • 提升Tumblr爬虫效率与功能
    本文介绍了对之前开发的Tumblr爬虫脚本进行升级,整合了两个脚本的功能,实现了自动分页爬取博客内容,并支持配置文件以下载多个博客的不同格式文件。此外,还优化了图片下载逻辑。 ... [详细]
  • 作为一名 Ember.js 新手,了解如何在路由和模型中正确加载 JSON 数据是至关重要的。本文将探讨两者之间的差异,并提供实用的建议。 ... [详细]
  • 本文详细介绍了在企业级项目中如何优化 Webpack 配置,特别是在 React 移动端项目中的最佳实践。涵盖资源压缩、代码分割、构建范围缩小、缓存机制以及性能优化等多个方面。 ... [详细]
  • 使用 GitHub、JSDelivr、PicGo 和 Typora 构建高效的图床解决方案
    本文详细介绍了如何利用 GitHub 仓库、JSDelivr CDN、PicGo 图床工具和 Typora 编辑器,搭建一个高效且免费的图床系统。通过此方案,用户可以轻松管理和上传图片,并在 Markdown 文档中快速插入高质量的图片链接。 ... [详细]
  • 随着网络安全威胁的不断演变,电子邮件系统成为攻击者频繁利用的目标。本文详细探讨了电子邮件系统中的常见漏洞及其潜在风险,并提供了专业的防护建议。 ... [详细]
  • 本文探讨了在通过 API 端点调用时,使用猫鼬(Mongoose)的 findOne 方法总是返回 null 的问题,并提供了详细的解决方案和建议。 ... [详细]
  • 本文详细介绍了 JavaScript 中的条件判断(if-else 和 switch)以及循环控制(for、while 和 do-while)。我们将探讨这些结构的基本语法、使用场景及注意事项,并补充一些实用技巧。 ... [详细]
  • 本文详细介绍如何在VSCode中配置自定义代码片段,使其具备与IDEA相似的代码生成快捷键功能。通过具体的Java和HTML代码片段示例,展示配置步骤及效果。 ... [详细]
  • 在网页开发中,页面加载速度是一个关键的用户体验因素。为了提升加载效率,避免在PageLoad事件中进行大量数据绑定操作,可以采用异步加载和特定控件来优化页面加载过程。 ... [详细]
author-avatar
抬头看着海
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有