博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
KOA框架铺垫之generator
阅读量:6573 次
发布时间:2019-06-24

本文共 7938 字,大约阅读时间需要 26 分钟。

在说generator之前,大家可能心里都有一个problem~

为什么不用express4.x而用KOA吗?
额,
所以,由于express本省建立在connect 插件上来的. 造成的结果是,connect拖慢了整体的步伐~ TJ大神也意识到了这个问题,在express4.x后,就直接将connect独立出来. KOA实际上,也是middleware的另外一种实现方式,只是他更快~
科普完毕~ 我们接着主题 how to use generator~
generator是KOA的基础,没有generator就没有KOA.

generator干嘛用的

一句话,我们可以把generator理解为可以暂停的函数. 这应该就是generator的所有内容. 我们先看一个demo吧

function* sayHello() {    var first,second;    yield first =  "jimmy";    yield second =  "sam";}var say = sayHello();say.next().value;say.next();say.next();

generatro的声明是使用*来进行的. 通过执行,返回一个generator对象,开始时,并未执行.通过调用next之后触发执行. 指定的返回值为yield后面的表达式.实际过程如图:

实际上,通过next返回的对象中,有两个属性-value,done,
next对象:

  • value: 即,指定yield后面的表达式的结果

  • done: Boolean, 用来表示该次连接是否结束.

function* sayHello() {    var first,second;    yield first =  "jimmy";    yield second =  "sam";}var say = sayHello();while(say.next().done){  //直接执行完毕即可}console.log('finish');

Communicate with generator

实际上,我们不光可以在外部调用next()来resume,generator的执行.而且还可以在外部传输参数,改变yield的返回值. 实际上,next()提供了我们这个权限.

function* sayHello() {    var first,second;    yield first =  "jimmy";    yield second =  "sam";    console.log(`first should be jimmy,but ${first}second should be sam, but ${second}`);}var say = sayHello();say.next().value;say.next('sam');say.next('jimmy');

通过next()传参,即可改变yield 表达式执行的结果.

另外提醒一下: next()调用只会执行yield后面的表达式,下一次调用时,才会执行完yield 所在行的表达式。
实际上,我们用一个公式表达.

next.done=yield+1

实际上如下图.

generator解决递归操作

所谓的递归操作(recursive), 实际上就是同一个函数,多次执行自身,并且将自身的结果引用.直到最后结束.

比如最经典的阶乘:

function fb(num) {    if (num <= 1) {        return 1;    }    return num * fb(--num)}

但是这样写,复杂度太高了. 实际上,我们就可以理解为,递归就是过去前一个函数的结果而已.

而,yield 恰恰可以完美的完成这一点.

function *cal(num){    var res = 1;    for(var i =1;i<=num;i++){        yield res = Mul(i,res);    }    console.log(res);}function Mul(num,res){    res = res*num;    return    res;}var fb = cal(3);fb.next();fb.next();fb.next();fb.next();

额,童鞋,先别急,如果你就单单就这么使用generator的话,那看起来真的很颓。这时候,我们只需要造一个轮子,使用一个函数来自动执行里面的结果. 为了说明这个demo,我们顺便再了解一个KOA提供的API-app.keys.

  • app.keys: 用来给Cookie进行签名密钥.实际上,他做的事就是防止cookie被篡改. 实际上,他就是利用Hmac或者对称加密(cipher/decipher), 加密cookie信息,然后通过返回对cookie做比较,就可以检验cookie是否被篡改了.

    通常,我们使用app.keys 自定义一个加密的数组keys( keys的长度,最好大于16,因为加密算法的最低是有要求的,否则他会先进行hash给你的key加密,然后在对原始数据加密 ).然后loop 加密.

//默认情况下,他的默认加密算法是  // defaults // _hash: 'sha256', // _cipher: 'aes-256-cbc',app.keys = ['koa you are beautiful','2333 kiss me baby'];//我们也可以手动更改:app.keys = new KeyGrip(['im a newer secret', 'i like turtle'], 'sha1');

使用怎样的算法,完全看自己的兴趣了. 不过推荐使用原始的,没必要给自己增加复杂度.

需要注意的是,我们设置app.keys是为了给cookie进行加密的.所以对于cookie使用加密,还有一点需要注意的.即,需要在cookie后面配置参数. sign:true

this.cookies.set('name', 'tobi', { signed: true });

实际上,keyGrip的思想就是多重加密,然后自动验重. 我们完全可以自己动手写一个. 这里,我们就通过试一试重现keyGrip的加密过程. 讲讲,generator到底是怎样解决递归的痛点.

const crypto = require('crypto');//加密function Sign(keys, data) {    this._algor = 'sha256';    this.keys = keys;    data = new Buffer(data, 'utf8');    var _this = this;    //使用generator 解决递归的效果    /**     * @yield {string} 加密的内容     * @key {array} 实际上就是加密的keys     * @describe  实际上generator解决递归其实使用该     * 函数就已经足够了.     */    function* run() {        var digest = _this.crypto(data, keys[0]);        for (var i = 1, len = keys.length; i < len; i++) {            yield digest = _this.crypto(digest, keys[i]);        }    }    console.log(this.exeGen(run).toString('hex'));}Sign.prototype.crypto = function(data, key) {    return crypto.createHmac(this._algor, key)        .update(data, 'binary')        .digest();}/** * @param  {generator} * @return 最后一个执行结果 * @author villainHR * @description 通过传入的generator函数,通过 * 检测next().done来完成结束的检测. */Sign.prototype.exeGen = function(gen) {    var gen = gen(),        val, res;    while (true) {        res = gen.next();        if (res.done) {            break;        } else {            val = res.value;        }    }    return val;}var sign = new Sign([ 'sma','sam','jimmy'], 'get');

不过实际中,如果想要挑战自己的童鞋, 自身递归的写法完全没有问题,但是,复杂度你懂的~

generator异步执行

说道异步,我们想到的肯定是Promise. 因为new Promise() && .then()几乎就可以拯救世界了. 而且,promise还提供 promise.all 神器. 但实际上,我们还可以使用generator来做一个伪promise.

function *all(){    var arr = Array.prototype.slice.call(arguments);    var cb = arr.pop();    yield arr.forEach((val)=>{val();});    cb();}var num = 0;var addNum = ()=>{num++;}var callNum = ()=>{console.log(num);}var gen = all(addNum,addNum,callNum);gen.next();gen.next();

使用gen.next();来手动启用异步执行的效果.最后将回调传输入,并输出即可.

但是,该情况还是没有解决我们的痛点.即,使用generator控制了异步之后,但是并不能写出优美的代码,像上文一样,我们需要手动调用gen.next();这样书写代码,可以说简直就是丑爆了~
TJ大神,自幼自练神功,随手写出一个co,巧妙的解决了这一个痛点.
实际上,co是一个很小的Module.他值提供了两个API:

  • co(fn*).then( val => ):使用promise.then来进行chain call.

const co = require('co');co(function* () {   return yield Promise.resolve(true);}).then((val)=>{    return val;}).then((val)=>{    return val;});

实际上,co内部将yield自动执行,并且返回yield后面的结果. 如果你想调用多个异步函数的话,就可以使用yield + Array的形式. 并且,co本身就返回了一个Promise.resolve(true);状态.

const co = require('co');var cbA = ()=>{console.log('A');}var cbB = ()=>{console.log('B');}var cbC = ()=>{console.log('C');}co(function* () {  var res = yield [    cbA(),cbB(),cbC()  ];}).then(()=>{    return Promise.reject('call reject')}).then(()=>{},(val)=>{    console.log(val);});

另外一个API:

  • var fn = co.wrap(fn*):实际上,就是将generator转换为一个普通函数进行调用.我们始终要记住一点,co帮我们干的最多的事,就是内置自动执行了gen.next()方法. 并且return gen.next().value。

const co = require('co');var cbA = ()=>{console.log('A');}var cbB = ()=>{console.log('B');}var cbC = ()=>{console.log('C');}var comFn = co.wrap(function* () {  var res = yield [    cbA(),cbB(),cbC()  ];});comFn.then(()=>{    return Promise.reject('call reject')}).then(()=>{},(val)=>{    console.log(val);});

经过测试,在MAC OX11上,创建一个co对象大概需要2.1ms的时间. 对性能的影响不算太大.但是,程序的可读性以及流畅性提升还是灰常大的.

说了这么多,目的就是给大家铺垫一下关于KOA的基本知识. 因为,KOA 的基本 写法和co类似. 都是建立在generator的基础上的.

说完了介绍,现在我们来正式看看KOA,他到底比以前的express4.x好在哪里.

初入KOA

在了解KOA之前,请,先下好KOA. 直接npm吧.我就不解释了.

看官方提供的helloword的demo:

const koa = require('koa');const app = koa();app.use(function *(next){    this.body = 'hello word~';    yield next;});app.listen(3000);

这应该是一个比较简单的demo。 但还没有体现出KOA的精华所在.KOA's key is 原本耦合但不得不分开写的程序,可以写在一起, 可读性和可调试性都不是同日而语的.

做一下解释吧,KOA其实就相当于一个捕获和冒泡的过程. 通过yield将一个函数分成两部分, 前面一块叫做upstream,后面一块叫做downstream. 当使用中间件执行时, 首先是upstream_A->upstream_B->upstream_C->...->upstream_N
然后返回: upstream_N->downstream_N->...->downstream_C->downstream_B->downstream_A. 差不多就是这样一个趋势. 我们来看一个demo吧:

const koa = require('koa');const app = koa();app.use(function *(next){    //upstream_A    console.log('upstream_A');    var time = new Date;    yield next;    //downstream_A    console.log(`downstream_A time is ${new Date - time}ms`);})app.use(function *(next){    //upstream_B    console.log('upstream_B');    var time = new Date;    yield next;    //downstream_B    console.log(`downstream_B time is ${new Date - time}ms`);})app.use(function *(next){    //upstream_C    console.log('upstream_C');    var time = new Date;    yield next;    //downstream_C    console.log(`downstream_C time is ${new Date - time}ms`);})app.use(function *(){  this.body = 'Hello World';});app.listen(3000);//最后的输出结果应该为:upstream_Aupstream_Bupstream_Cdownstream_C time is 6msdownstream_B time is 7msdownstream_A time is 8ms

根据结果,可以看出upstream和downstream的执行顺序.看一个总结图吧

如何自己动手写一个中间件

我们大致了解了app.use之后,完全可以自己动手写一个中间件. 中间件其实就是一个generator函数.

const koa = require('koa');const app = koa();const logger = function *(next){    var time = new Date;    yield next;    this.allTime = time - new Date + 'ms';}app.use(logger);app.use(function *(){    this.body = "ok~";})app.listen(3000);//或者直接导出一个模块exports.logger = function *(next){    var time = new Date;    yield next;    this.allTime = time - new Date + 'ms';}

而且,如果你觉得你的middleware太大,想要拆分成不同的middleware,这时候,就可以使用call(this,next).

function *Cal1(next){    console.log(1);    yield next;}function *Cal2(next){    console.log(2);    yield next;}function *Cal3(next){    console.log(3);    yield next;}function *CalAll(next){     yield Cal1.call(this,Cal2.call(this,Cal3.call(this,next)));}

这差不多就是KOA的精髓所在. KOA 通过next的方式,将一些本来需要拆分但功能一直的代码块,可以连接在一起. 有兴趣的同学,可以参考.

转载请注明作者和原文链接:

你可能感兴趣的文章
大整数加法
查看>>
下拉菜单
查看>>
C/C++中extern关键字详解
查看>>
[清华集训2014]玛里苟斯
查看>>
Doctype作用?严格模式与混杂模式如何区分?它们有何意义
查看>>
jquery选择器(可见对象,不可见对象) +判断,对象(逆序)
查看>>
0029-求最小的数
查看>>
【MVC+EasyUI实例】对数据网格的增删改查(上)
查看>>
Socket编程
查看>>
python2.7_1.14_编写一个简单的回显客户端/服务器应用
查看>>
Android-Handler更新View加线程
查看>>
第三章:如何建模服务
查看>>
EF CodeFirst下数据库更新
查看>>
HDU - 4803 - Poor Warehouse Keeper (思维)
查看>>
Codeforces 839B - Game of the Rows
查看>>
Project Euler 345: Matrix Sum
查看>>
php fpm安装curl后,nginx出现connect() to unix:/var/run/php5-fpm.sock failed (13: Permission denied)的错误...
查看>>
mysql允许远程登录
查看>>
js判断undefined类型
查看>>
问题账户需求分析
查看>>