KOA框架鋪墊之generator

在說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吧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');

Communicate with generator

實際上,咱們不光能夠在外部調用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

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_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

相關文章
相關標籤/搜索