在说generator以前,你们可能内心都有一个problem~
为何不用express4.x而用KOA吗?
额,您直接看结果吧~
因此,因为express本省创建在connect 插件上来的. 形成的结果是,connect拖慢了总体的步伐~ TJ大神也意识到了这个问题,在express4.x后,就直接将connect独立出来. KOA实际上,也是middleware的另一种实现方式,只是他更快~
科普完毕~ 咱们接着主题 how to use generator~
generator是KOA的基础,没有generator就没有KOA.算法
一句话,咱们能够把generator理解为能够暂停的函数. 这应该就是generator的全部内容. 咱们先看一个demo吧express
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对象:npm
value: 即,指定yield后面的表达式的结果segmentfault
done: Boolean, 用来表示该次链接是否结束.数组
function* sayHello() { var first,second; yield first = "jimmy"; yield second = "sam"; } var say = sayHello(); while(say.next().done){ //直接执行完毕便可 } console.log('finish');
实际上,咱们不光能够在外部调用next()来resume,generator的执行.并且还能够在外部传输参数,改变yield的返回值. 实际上,next()提供了咱们这个权限.promise
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 所在行的表达式。
实际上,咱们用一个公式表达.cookie
next.done=yield+1app
实际上以下图.koa
所谓的递归操做(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');
不过实际中,若是想要挑战本身的童鞋, 自身递归的写法彻底没有问题,可是,复杂度你懂的~
说道异步,咱们想到的确定是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. 直接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_A upstream_B upstream_C downstream_C time is 6ms downstream_B time is 7ms downstream_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的方式,将一些原本须要拆分但功能一直的代码块,能够链接在一块儿. 有兴趣的同窗,能够参考koa官网.
转载请注明做者和原文连接:http://www.javashuo.com/article/p-cnzkskhs-kp.html