美文网首页KoaWeb前端之路后端
三英战豪强,思绪走四方。浅谈我眼中的express、koa和ko

三英战豪强,思绪走四方。浅谈我眼中的express、koa和ko

作者: 白昔月 | 来源:发表于2017-04-21 11:24 被阅读7753次

    前言

    跟好朋友打赌,我要来个技术文章日更。于是,我跑到到群里喊了句,我要日更。没想到,得到的是大家的支持与鼓励。感谢小伙伴们,逼着我上了梁山......
    此文,就是我日更文章的开始,不知道自己能坚持多久。最开始打算将文章标题名定为《围绕node60天》,一个叫老张的网友看了一下,随口说了句短小搓...呵呵呵...短小搓是什么鬼?于是,我给文章起了个高大上的名字,《诺的那些事》......这次够高大上了吧,哈哈哈哈。本来想给浅谈我眼中的express、koa和koa2定为副标题的,可恨简书本着回归写作根本的理念,不存在副标题这个功能,饮恨了。

    我是个node程序开发者。此前,我一直认为自己是一个坚定的java信徒,我感觉java才是最终改变世界的语言,甚至我出国旅游去的都是java island......在用了7年Java之后,我遇见了node。遇见node的那一瞬间,我好似看见了蜂腰肥臀的妙龄少女,一群群一片片的在我面前欢歌笑舞,我知道,是时候放弃java了,java就这样渐渐成了往事。不过,我跟java肯定还是藕断丝连的,毕竟hadoop这样的程序还是需要用java来写的,另外,7年的武功是说什么也忘不了了。到现在为止,我用了3年node,见证了node的成长,也见证了自己的成长。我准备好好说说node的那些事,跟大家分享,接受大家的意见和建议。

    Nodejs-logo

    跟很多人一样,我是以express开启自己的node之旅的。express是什么呢?它是一个封装了Connect的、并提供web服务的中间件,是开发web程序的利器。express是由TJ大神开发的,之后,TJ大神又开发了koa这款神器。但是,因为,es7的飞速发展,koa又迅速衍化出了koa2这个版本,时至今日,koa的github已经全面更换为了koa2版本的代码,当然,这一切,还真就是在我眼前发生的,作为历史的见证者,我要谈谈我对这几个框架的看法。

    说个题外话,我的文章,不打算走传统技术文章的套路,我讲就讲一些不一样的,讲一些平常你看不到的东西,希望各位技术大牛多多给小弟指点,在下这厢有礼了。

    酷事

    单田芳老爷子,有几部超级好听的长篇评述,叫三侠五义,其中,有个重要的人物,白眉大侠徐良(刀是什么样的刀,金丝大环刀,剑是什么样的剑,闭月羞光剑......),白眉大侠徐良是一个早产儿,从小体弱多病,但是最终凭借着刻苦努力,成为了一代武术宗师。node的经历跟白眉大侠很像,node也是个早产儿。早期的node问题多多,那个时候,在win上安装node简直就是一件灾难。不过,随着时间的推移,node变得越来越好用,node的好基友npm也变得越来越庞大。我从node4.0开始入门,后来经历了node6.0版本的各种无奈和妥协,紧接着就迎来了node7.6版本的绝地反击,直到现在,感受到了node7.9的大彻大悟。

    记得当初和一个技术猿聊天,他跟我抱怨,node里全是大坑。我一听,这位仁兄用的是express吧,这位仁兄告诉我,没有,他们自己实现了个轮子,跟express差不多,现在正在考虑着升级的事宜呢。呵呵呵了,造了个轮子,还跟express差不多。不过,这也从另外一个侧面看出了node当年不可回避的几个问题:

    1.node早期就是个极客玩具,高手众多,产品野蛮生长。
    2.使用node开发商业软件是一件正在发生的事情。(对了,阿里、腾讯、非死不可、领英这些大佬也都用node重构了一些适合的模块。)
    3.那个时候,使用node的同志们不是傻,就是用鸡汤晃点了他们老板。

    回调大坑

    不过这个仁兄倒是也说出了node的一些痛点,其中,node存在的技术大坑可能大家都遇到过,这些坑是因为node的语言特性导致的。例如,回调大坑,就是这些坑中,最深的一个。

    下边是一个回调大坑的代码示例,我们来感受一下:

    module.exports = function (param, cb) {
      asyncFun1(param, function (er, data) {
        if (er) return cb(er);
        asyncFun2(data,function (er,data) {
          if (er) return cb(er);
          asyncFun3(data, function (er, data) {
            if (er) return cb(er);
            cb(data);
          })
        })
      })
    }
    

    node本身是异步回调的,通过高阶函数,偏函数实现回调函数。但是,回调函数嵌套过多,会使代码不可阅读、不可描述,那位仁兄说的大坑便是callback hell,翻译过来就是回调大坑,或者说是Pyramid of Doom,邪恶金字塔。(我朴素的认为这位仁兄只遇到了这一个大坑.....呵呵呵)

    回调大坑怎么解决呢?es5可以利用一下第三方库,例如async库,或者单纯使用connect中间件提供的next功能来处理,还可以利用promise来处理回调大坑。当然,单纯使用promise可能给自己带来另外一个大坑,then大坑,或者叫pipe大坑,无数个then,想想也是够恐怖的。另外,还可以使用node自带的事件模块来处理回调问题,利用事件代理(我记得是backbone的一个模块)来简化代码书写。关于事件模块,我之后会写个小专题,来说说node的事件原理。不过,虽然提到了事件模块,但是,我不推荐用事件去处理回调嵌套,因为,需要写更多的代码,得不偿失。

    这里说个题外话,在朴大人(朴灵)的一篇文章中,提到了wind库和step库,此处就不进行介绍了,因为,es6和es7会给我们更加好的使用体验。

    es5讲完了,各种基于es5来处理回调的方法也讲完了。其实,我在使用这些方法来简化回调嵌套的时候,总感觉是脱了裤子放屁,讲真,有的时候,不仅没有简化代码,还会造成其他的代码阅读障碍,增加团队的学习成本。归根结底,我们的代码是写给人看的,机器只是顺便执行一下而已。

    ES的官方组织,肯定认识到了这一点,于是,基于协程原理的规范也呼之欲出,终于在ES6中为大家带来了Generator函数。

    Coroutine,协程,简单来说就是由用户通过特定的程序语言控制CPU切换和挂起进程(Process)或者线程(Thread),用同步的方式来模拟异步程序,我之后会单独讲一下进程、线程、协程,在此不做过多展开。既然提供了协程的功能,那么我们处理起回调也就迎刃而解了。

    Generator函数和yield语句是一对好基友,如果没有yield语句的话,Generator函数只不过是暂缓执行的状态机而已。通过配合yield,Generator 函数就可以暂停执行和恢复执行,从而将其内部封装的异步函数变为同步执行。下面我们看看例子来感受一下:

    function* gen(x){
      var y = yield x + 2;
      return y;
    }
    
    var g = gen(1);
    g.next() // { value: 3, done: false }
    g.next(2) // { value: 2, done: true }
    

    当然Generator函数还有些滥用之嫌,具体为什么,我会在后续的文章中做出解释。反正,ES官方组织他们对于Generator函数是不满意的。于是,ES官方组织马不停蹄,终于在ES7规范中,捣鼓出来了async/await这个目前为止,异步回调最佳的解决方案。

    本质上讲,async/await规范是Generator函数+yield语句的语法糖。返回部分都是一个Promise对象。async/await规范比Generator函数+yield语句要更加好用,下面我们看看例子来感受一下:

    var sleep = function (time) {
        return new Promise(function (resolve, reject) {
            setTimeout(function () {
                resolve();
            }, time);
        })
    };
    
    var go= async function () {
        console.log('start');
        await sleep(3000);
        console.log('end');
    };
    
    go();
    

    至此,回调大坑的问题算是得到了很好的解决,我们接下来,就说是express、koa和koa2这三个框架。

    TJ大神的三大英雄

    TJ大神

    每每看到TJ大神的头像,我都会想起一个词:preconception。偏见,先入之见。许多人,对于程序员是存在很大的偏见的。大多数人的脑海中给程序员的定义都是这个样子:

    图片来源于网络,加班的程序员们

    一般人认为程序员:不够健康,不够整洁,不够潮流,人傻,钱多......TJ大神,这个顶级程序员,则给我们上了非常生动的一课。TJ是设计师出身,半路出家做了程序员,他一个人完成了express、koa、koa2设计和核心开发。TJ曾经说过,他之所以能做出这些NB的软件,是因为,他热爱阅读其他大牛的源码,他会把自己不明白的问题都弄懂。他在第一时间遇见了问题,处理了问题,保证自己深刻理解各种软件的核心原理和运行机制。于是,TJ就变成了一个npm包贡献极多的node大神,他的光辉和事迹最终会变为传说,再node圈里永久的流传下去......

    三大英雄

    node的早期,是荒芜的年代,正如之前我的那个哥们一样,那个时候,没有轮子。程序员自己制造了各种各样的轮子,真可谓是八仙过海,各显神通。那个时候node程序员一般这样开始写web应用:

    var http = require('http');
    
    http.createServer(function (request, response) {
        response.writeHead(200, {'Content-Type': 'text/plain'});
        response.end('Hello World\n');
    }).listen(8888);
    
    console.log('Server running at http://127.0.0.1:8888/');
    

    终于,TJ打造了express、koa、koa2三大英雄,node的浪漫主义年代逐渐揭开了序幕。我们看下边的表:

    英雄 说明 对应 经典
    express web框架 es5 回调嵌套
    koa web框架 es6 Generator函数+yield语句+Promise
    koa2 web框架 es7 async/await+Promise

    下面我就开始说一下这三个框架和他们之间千丝万缕的联系

    初代英雄:express

    初代英雄:express

    express的入门非常简单,通过创建express的Application就构建了一个expressweb实例。下面我们看看例子来感受一下:

    var express = require('express');
    var app = express();
    
    app.get('/', function (req, res) {
      res.send('Hello World!');
    });
    
    var server = app.listen(3000, function () {
      var host = server.address().address;
      var port = server.address().port;
    
      console.log('Example app listening at http://%s:%s', host, port);
    });
    

    express本身封装了路由模块,因此,可以利用express直接处理各种http路由请求。

    在express用四个主要模块:

    模块 说明 解释
    Application web服务器模块 Application 抽象了web服务器的主要贡呢和接口,如监听、事件、加载中间件、get\post请求等
    Request 请求 Request
    Response 响应 Response
    Router 路由 Router

    express用Application、Request、Response、Router四个主要模块,模拟了一个完整的web服务器功能,对了,express还在相当长的一段时期中受到了Connect的影响。在使用express的过程中,你会发现express是一个极简的、灵活的 web 应用开发框架,它提供的这一系列强大的特性,可以帮助你快速创建各种 web 和移动设备应用。

    二代英雄:koa

    二代英雄:koa

    目前的koa官方github已经全面的使用koa2版本的代码了,换句话说,koa和koa2现在只是版本上的区别了,koa是老版本,koa2用新的版本号。因此,koa1,我们需要查看老的代码版本。

    koa 是由 express原班人马打造的(TJ),致力于构建更小、更富有表现力、更健壮的 web 框架。使用 koa 编写 web 应用,通过组合不同的 generator,可以免除重复繁琐的回调函数嵌套,并极大地提升错误处理的效率。koa 不在内核方法中绑定任何中间件,它仅仅提供了一个轻量优雅的函数库,使得编写 koa 应用变得得心应手。

    Koa 包含了像 content-negotiation(内容协商)、cache freshness(缓存刷新)、proxy support(代理支持)和 redirection(重定向)等常用任务方法。 与提供庞大的函数支持不同,Koa只包含很小的一部分,因为Koa并不绑定任何中间件。

    koa中也包含4个主要模块,Application、Request、Response、Context。此时,router已经被排除在内核之外了。其实,koa只是一个“中间架”,几乎所有的功能都需要由第三方中间件来协同完成。例如koa的router模块,就有20多个,优胜劣汰,自由选择......虽然有不规范之嫌,但是,koa是规范的这就足够了。使用koa,可以最大限度的发挥自己的想象力,利用koa,构建各种个性化的web与移动应用。下面我们看看例子来感受一下:

    var koa = require('koa');
    var app = koa();
    
    app.use(function *(){
      this.body = 'Hello World';
    });
    
    app.listen(3000);
    

    没错,就是这么简单,使用了Generator函数,这也是koa和express最大的不同,express是回调函数,koa是用Generator来作为响应器的。

    另外,那个替代了router的context是怎样的呢?下面我们看看例子来感受一下:

    app.use(function *(){
      this; // is the Context
      this.request; // is a koa Request
      this.response; // is a koa Response
    });
    

    另外,koa中还有co这个工具。co是一个“皮”,通过co来包装Generator和yeild,下面我们看看例子来感受一下:

    var co = require('co');
     
    co(function *(){
      // yield any promise 
      var result = yield Promise.resolve(true);
    }).catch(onerror);
     
    co(function *(){
      // resolve multiple promises in parallel 
      var a = Promise.resolve(1);
      var b = Promise.resolve(2);
      var c = Promise.resolve(3);
      var res = yield [a, b, c];
      console.log(res);
      // => [1, 2, 3] 
    }).catch(onerror);
     
    // errors can be try/catched 
    co(function *(){
      try {
        yield Promise.reject(new Error('boom'));
      } catch (err) {
        console.error(err.message); // "boom" 
     }
    }).catch(onerror);
     
    function onerror(err) {
      // log any uncaught errors 
      // co will not throw any errors you do not handle!!! 
      // HANDLE ALL YOUR ERRORS!!! 
      console.error(err.stack);
    }
    

    虽然,前边说了很多express、koa的相关知识,但是,这两个都不重要了,随着koa2的扶正和node 7.6的发布,基于nodejs的程序开发,开启了新的篇章。

    三代英雄:koa2

    三代英雄:koa2

    上一节已经提到,目前的koa官方github已经全面的使用koa2版本的代码了,并且有一句非常重要的提示Koa requires node v7.6.0 or higher for ES2015 and async function support.。意思是说,koa需要至少node v7.6.0版本和ES2015(es6+async)才能使用。这个提示,也是非常重要的一句话,从这个版本开始,我们可以抛弃Bable(当然,nodev7.6还是不能完全抛弃babel,因为到目前为止,node都还没有实现对import和export的支持,感谢深蓝wbe的提醒),快乐的使用async等新的语法了。(Babel 自带了一组 ES2015 语法转化器。这些转化器能让你现在就使用最新的 JavaScript 语法,而不用等待浏览器和node提供支持。)

    目前,koa2结合了async/await已经成为了最好的web开发框架。上一节,已经讲了koa的主要模块和实现原理,此处,我只是简单说说koa2和koa不同之处,下面我们看看例子来感受一下:

    const Koa = require('koa');
    const app = new Koa();
    
    app.use(ctx => {
      ctx.body = 'Hello World';
    });
    
    app.listen(3000);
    

    函数式编程,async/await功能,程序简单,好用,真可谓是居家旅行的不二之选呀。通过查看代码,koa2去除了co中间件,进一步的精简了内核,这一点也正好符合当下性冷淡风格的设计潮流......不禁想赞叹一句,TJ不愧是设计师出身呀......

    小结

    其实,很多人不关心框架的原理,他们关心的是如何从express升级到Koa,如何从koa升级到koa2。虽然koa
    对于向前兼容的并不好,但是,我在这里想提出另外一种思路,就是利用express+async/await的形式来做升级。一方面,可以在不改变原有程序的基础上使用最新的语法特性,另一方面,可以用最小的代价获取最大的效益。下面我们看看例子来感受一下:

    var PageQuery = async (page, pageSize, Model, populate, queryParams, sortParams) => {
        var start = (page - 1) * pageSize;
        var $page = {
            pageNumber: page
        };
    
        let TotleRow = await ModelCount(Model, queryParams);
        let records = await PageRecords(Model, queryParams, start, pageSize, populate, sortParams);
    
        $page.TotleRow = TotleRow;//(count - 1) / pageSize + 1;
        $page.PageCount=parseInt(TotleRow/pageSize)+1;
        $page.results = records;
        return $page;
    };
    
    var ModelCount = (Model, queryParams) => {
        return Model.count(queryParams).exec().then((count) => {
            return count;
        }).catch((err) => {
            console.log("err:" + err);
            return err;
        });
    }
    
    var PageRecords = (Model, queryParams, start, pageSize, populate, sortParams) => {
        return Model
            .find(queryParams)
            .skip(start)
            .limit(pageSize)
            .populate(populate)
            .sort(sortParams)
            .exec()
            .then((doc) => {
                return doc;
            }).catch((err) => {
                console.log("err:" + err);
                return err;
            });
    }
    
    //......
    Customer.PageQuery(pageNum, pageSize, Customer.Model, "", {}, {}).then((pageResult) => {
    
            res.render('customersList', {
                layout: "admin",
                customersList: pageResult.results,
                totalPages: pageResult.PageCount,
                pno: pageNum,
            });
        }).catch((err) => {
            console.log("err:" + err);
            res.send("err");
        });
    
    

    上边是我写的一个中间件,可以通过这种简单的方式,来处理express与es7的升级问题。(完整代码可以到我的github上去查看https://github.com/lxlhum/meet_quick ,大家帮我把星星点起来哈)

    尾声

    流行的web技术统计:node、ruby、python、php、java

    现在,node的社区非常活跃,产品换代升级非常迅速和及时。使用node,既要求我们用扎实的功底,又要求我们与时俱进,不断学习。毕竟,不管用什么语言,在程序开发的道路上,只有不断学习,才能不断前进。

    这部分关于node的基础知识,可以看我写的一个笔记:深入浅出NodeJS的读书笔记,保证您读过以后,会有豁然开朗之感。笔记中,记录了tcp/ip,http,socket/websocket的相关知识,全方位的介绍nodejs。

    相关文章

      网友评论

      • 李章鱼:写的很棒,感觉文风很洒脱,一看评论发现博主想写小说,哈哈,加油
      • 海_af41:我滴妈呀, 这样的都有人赞赏.
        白昔月:@海_af41 有什么问题没?说说
      • 9ecde7f75952:楼主,nodev7.6还是不能完全抛弃babel,因为到目前为止,node都还没有实现对import和export的支持
        白昔月:@深蓝之WEB 是的 ,我修改一下
      • 7af40caf4b61:想学习你的https://github.com/lxlhum/meet_quick ,但是git下来没有配置文件啊,/profile.json能不能传上去呢? 好人谢谢啦
        白昔月:@Jacky1996 {
        "token": "你的微信token",
        "appid": "你的微信appid",
        "appsecret": "你的微信appsecret",
        "encodingAESKey": "你的微信encodingAESKey",
        "checkSignature": "true",
        "db_name":"meet_db",
        "db_conection":"mongodb连接地址",
        "models_factary":"../models/models_factary.js",
        "UserModel":"../models/userModel.js",
        "CustomerModel":"../models/customerModel.js",
        "ActivityModel":"../models/activityModel.js",
        "mp":"Middleware Program",
        "login_mp":"../handlers/login.js",
        "main_mp":"./handlers/main.js",
        "main_mp2":"../handlers/main.js",
        "wechat_event":"../handlers/wechat_event.js",
        "customer_mp":"../handlers/customers.js",
        "path_wechat":"/home/userp/meet_quick/wechat/wechat_temp_qr/"
        }
        7af40caf4b61:@白昔月 谢谢啦,在学习你的文章,写的真好!
        白昔月:配置文件 是数据库设置什么的 我回头私信给你吧 把数据库之类的改成你自己的就行了
      • yohance:大神啊!node的经理这么丰富,我也是java刚转前端不久赞一个
        白昔月:@yohance node很好玩,加油加油
      • 8c8f3d9dba8a:“更”是无辜的
        白昔月: @东尼大兔 唉
      • 209bd3bc6844:用 babel编译后,express也可以使用async/await啊。为什么都说express的缺点是回调。babel不就解决了吗?
        白昔月:对的,因为,很多文章都是babel出现之前写的,我这边也通过新版本的node解决回调问题了,这个就好像口碑一样。另外,技术一直在发展,学新的框架可以在求职招聘中获得更好的收益。
      • slimsallen:是不是 应该 学koa了
        slimsallen:@白昔月 好的 谢啦
        白昔月:koa的内核也特别简单,很小,建议有时间看看
        白昔月:koa很简单的 ,我搭建好了koa2的学习框架了,发给你看看,是一个koa web demo
        https://github.com/lxlhum/lxlhumkoa
      • 万小檬:写得不错,继续保持更新咯😀
        2rd:@白昔月 好的技术猿从来都是不务正业的🤔
        白昔月:最近在写简书上的征文.....停止了技术文章更新。我要写一个青春小说,我要成为作家.....呵呵呵
      • succpeking:看到了文章的开头,还是挺看好,但是看完这篇也不好说啥了:smiley:
        只能说好好继续加油。。。
        白昔月:明白了,继续加油
      • c433e6d85ffc:读2遍👍

      本文标题:三英战豪强,思绪走四方。浅谈我眼中的express、koa和ko

      本文链接:https://www.haomeiwen.com/subject/wnqlzttx.html