RubyLouvre / agate

灵活易用的nodejs后端框架
GNU General Public License v2.0
74 stars 21 forks source link

koa源码学习2 #7

Open RubyLouvre opened 9 years ago

RubyLouvre commented 9 years ago
var co = require("co")

var delay = function(a){
    return function(fn){
        var now = new Date 
         setTimeout(function(){
           fn(null, new Date - now)
        }, a)
    }
}
co(function* () {
   var a = yield delay(1000);
   var b = yield delay(1500);
   return [a,b];
}).then(function (value) {
   console.log(value);
}, function (err) {
   console.error(err.stack);
});

image

RubyLouvre commented 9 years ago

co 要求传入一个生成器函数, 然后让里面的所有异步方法同步执行(从上往下执行),最后返回一个Promise

var co = require("co")

var delay = function(a){
    return function(fn){
        var now = new Date 
         setTimeout(function(){
           console.log(a + "--------")
           fn(null, new Date - now)
        }, a)
    }
}
co(function* () {
   var a = yield delay(1000);
   console.log("执行完a")
   var b = yield delay(1500);
   console.log("执行完b")
  return [a,b];
}).then(function (value) {
  console.log(value);
}, function (err) {
  console.error(err.stack);
});

image

RubyLouvre commented 9 years ago

co方法只是让参数函数的内部变成一种类似PHP同步环境, 但不方便我们往这个环境传参

因此就有了co.wrap, 这也是koa里面使用的方法

将上面的例子改一下

var co = require("co")

var delay = function(a){
    return function(fn){
        var now = new Date 
         setTimeout(function(){
           console.log(a + "--------")
           fn(null, new Date - now)
        }, a)
    }
}

var fn = co.wrap(function* (val) {
   var a = yield delay(val);
   console.log("执行完a")
   var b = yield delay(val);
   console.log("执行完b")
   return [a,b];
});

fn(1000).then(function (value) {
  console.log(value);
}, function (err) {
  console.error(err.stack);
});
RubyLouvre commented 9 years ago

co方法只是让参数函数的内部变成一种类似PHP同步环境, 但不方便我们往这个环境传参

因此就有了co.wrap, 这也是koa里面使用的方法

将上面的例子改一下

var co = require("co")

var delay = function(a){
    return function(fn){
        var now = new Date 
         setTimeout(function(){
           console.log(a + "--------")
           fn(null, new Date - now)
        }, a)
    }
}

var fn = co.wrap(function* (val) {
   var a = yield delay(val);
   console.log("执行完a")
   var b = yield delay(val);
   console.log("执行完b")
   return [a,b];
});

fn(1000).then(function (value) {
  console.log(value);
}, function (err) {
  console.error(err.stack);
});

xxxx

RubyLouvre commented 9 years ago

我们再看koa的核心代码

app.callback = function(){
  var mw = [respond].concat(this.middleware);
  var fn = this.experimental
    ? compose_es7(mw)
    : co.wrap(compose(mw));
  var self = this;

  if (!this.listeners('error').length) this.on('error', this.onerror);

  return function(req, res){
    res.statusCode = 404;
    var ctx = self.createContext(req, res);
    onFinished(res, ctx.onerror);
    fn.call(ctx).catch(ctx.onerror);
  }
};

mw 为一个装着许多生成器函数的数组 this.experimental是es7用的,因此只会走co.wrap分支

上面的分码可以简化为

app.callback = function(){
  var mw = [respond].concat(this.middleware);
  var fn = co.wrap(compose(mw));
  var self = this;

  if (!this.listeners('error').length) this.on('error', this.onerror);

  return function(req, res){
    res.statusCode = 404;
    var ctx = self.createContext(req, res);
    onFinished(res, ctx.onerror);
    fn.call(ctx).catch(ctx.onerror);
  }
};
RubyLouvre commented 9 years ago
var co = require("./co")
var compose = require("./compose")
function *m1(next){
  var start = new Date;
  console.log("start=======1111");
  yield next;
  console.log("end=======1111");
  var ms = new Date - start;
  console.log('%s %s - %s', this.method, this.url, ms);
}
function *m2(next){
  var start = new Date;
  console.log("start=======2222");
  yield next;
  console.log("end=======2222");
  var ms = new Date - start;
  console.log('%s %s - %s', this.method, this.url, ms);
}
function *m3(next){
  var start = new Date;
  console.log("start=======3333");
  yield next;
  console.log("end=======3333");
  var ms = new Date - start;
  console.log('%s %s - %s', this.method, this.url, ms);
}
var list = [m1, m2, m3]

var fn = co.wrap(compose(list));

fn.call({method: "get", url: "localhost"}).then(function (value) {
  console.log(value);
}, function (err) {
  console.error(err.stack);
});
RubyLouvre commented 9 years ago

改得更像些应该是这样

var co = require("./co")
var compose = require("./compose")

function *respond(next) {
  console.log("first !")
  var last = yield *next;
  console.log(last+"!")
}
function *m1(next){
  var start = new Date;
  console.log("start=======1111");
  yield next;
  console.log("end=======1111");
  var ms = new Date - start;
  console.log('%s %s - %s', this.method, this.url, ms);
  return "last "
}
function *m2(next){
  var start = new Date;
  console.log("start=======2222");
  yield next;
  console.log("end=======2222");
  var ms = new Date - start;
  console.log('%s %s - %s', this.method, this.url, ms);
}
function *m3(next){
  var start = new Date;
  console.log("start=======3333");
  yield next;
  console.log("end=======3333");
  var ms = new Date - start;
  console.log('%s %s - %s', this.method, this.url, ms);
}
var list = [respond, m1, m2, m3]

var fn = co.wrap(compose(list));

fn.call({method: "get", url: "localhost"}).catch(function (err) {
  console.error(err.stack);
});
RubyLouvre commented 9 years ago

var co = require("./co")
var compose = require("./compose")
function delay(time) {
  return function(fn) {//这是一个普通函数,使用thunkToPromise
    setTimeout(function() {
       fn(null, time)//null表示没有错
    }, time)
  }
}

co(function* () {
 var a = yield delay(200)
 a = yield delay(a + 100)
 a = yield delay(a+ 150)
 return a
}).then(function(time) {
    console.log(time)
})
RubyLouvre commented 9 years ago

加入 定时器

var co = require("./co")
var compose = require("./compose")

function *respond(next) {
  console.log("first !")
  var last = yield *next;
  console.log(last+"!")
}
function *m1(next){
  var start = new Date;
  console.log("start=======1111");
  yield next;
  console.log("end=======1111");
  var ms = new Date - start;
  console.log('%s %s - %s', this.method, this.url, ms);
  return "last "
}
function *m2(next){
  var start = new Date;
  console.log("start=======2222");
  yield next;
  console.log("end=======2222");
  var ms = new Date - start;
  console.log('%s %s - %s', this.method, this.url, ms);
}
function delay(time) {
  return function(fn) {//这是一个普通函数,使用thunkToPromise
    setTimeout(function() {
        console.log("setTimeout")
       fn(null, time)//null表示没有错
    }, time)
  }
}
function *m3(next){
  var start = new Date;
  console.log("start=======3333");
  yield delay(300)
  console.log("start=======333 setTimeout");
  yield next;
  console.log("end=======3333");
  var ms = new Date - start;
  console.log('%s %s - %s', this.method, this.url, ms);
}
var list = [respond, m1, m2, m3]

var fn = co.wrap(compose(list));

fn.call({method: "get", url: "localhost"}).catch(function (err) {
  console.error(err.stack);
});
RubyLouvre commented 9 years ago

将一个普通的node异步方法改成可以yield的方法 可以使用以下方式

var helper = function (fn) {
  return function () {
    var args = [].slice.call(arguments);
    var pass;
    args.push(function () { // 在回调函数中植入收集逻辑
      if (pass) {
        pass.apply(null, arguments);
      }
    });
    fn.apply(null, args);

    return function (fn) { // 传入一个收集函数
      pass = fn;
    };
  };
};

var readFile = function(filename) {  
  return function(cb){  
    fs.readFile(filename,cb);  
  }  
};  

或者使用thunkify

var co = require("./co")
var compose = require("./compose")
var thunkify = require("thunkify")
var fs = require("fs")
function *respond(next) {
  console.log("first !")
  var last = yield *next;
  console.log(last+"!")
}

function *m1(next){
  var start = new Date;
  console.log("start=======1111");
  yield next;
  console.log("end=======1111");
  var ms = new Date - start;
  console.log('%s %s - %s', this.method, this.url, ms);
  return "last "
}
function *m2(next){
  var start = new Date;
  console.log("start=======2222");
  yield next;
  console.log("end=======2222");
  var ms = new Date - start;
  console.log('%s %s - %s', this.method, this.url, ms);
}

function bind(fn, context){
    var args = [].slice.call(arguments, 2)
    return function *(fn){
        args.push(function (err, e){
            console.log("+++++++++"+fn)
            //yield e
        })
        console.log(args)
        fn.apply(context, args)
    }
}
function delay(time) {
  return function(fn) {//这是一个普通函数,使用thunkToPromise
    setTimeout(function() {
        console.log("setTimeout")
       fn(null, time)//null表示没有错
    }, time)
  }
}

function *m3(next){
  var start = new Date;
  console.log("start=======3333"+thunkify);
  var txt = yield thunkify(fs.readFile)( './compose.js',"utf8")
  console.log(txt);
  yield next;
  console.log("end=======3333");
  var ms = new Date - start;
  console.log('%s %s - %s', this.method, this.url, ms);
}
var list = [respond, m1, m2, m3]

var fn = co.wrap(compose(list));

fn.call({method: "get", url: "localhost"}).catch(function (err) {
  console.error(err.stack);
});
RubyLouvre commented 9 years ago

串行与并行

var thunkify = require('thunkify');  
var co = require('co');  
var fs = require('fs');  

var readFile = thunkify(fs.readFile);  

co(function *() {  
  var now = new Date - 0
   var a = yield readFile('./readme.md');   
  console.log(a.length)
   var b = yield readFile('./fwp_core.zip');  
  console.log(b.length)
  var c = yield readFile('./package.json');  
  console.log(c.length)
  console.log(new Date - now)
})
var thunkify = require('thunkify');  
var co = require('./co');  
var fs = require('fs');  

var readFile = thunkify(fs.readFile);  

co(function *() {  
  var now = new Date - 0
   var a = yield [readFile('./readme.md'),readFile('./fwp_core.zip'), readFile('./package.json')];   
   console.log(a[0].length, a[1].length, a[2].length)
  console.log(new Date - now)
})

koa实现bigpipe的核心代码

var koa = require('koa');  
var Readable = require('stream').Readable
var koa = require('koa');  
var thunkify = require("thunkify")
var fs = require("fs")
var app = koa();

app.use(function *(){  
     this.type = 'text/html'
     var stream = this.body = new Readable()
     stream._read = function () {}
     var readFile = thunkify(fs.readFile);  
     var a = yield [readFile('./start.html',"utf8"),readFile('./end.html',"utf8")];   
     stream.push(a[0] + '<br>');
     setTimeout(function(){
        stream.push('第二行<br>');
     }, 1200)
     setTimeout(function(){
        stream.push(a[1] + '最后一行<br>');
        stream.push(null)
    }, 2200)
});

app.listen(3000);  

http://www.bitscn.com/school/Javascript/201405/200167_2.html

RubyLouvre commented 9 years ago

index

https://github.com/koajs/bigpipe-example/tree/master/client https://gemnasium.com/npms/bigpipe-example http://www.jianshu.com/p/12cfa4ba29d4 https://github.com/undoZen/bigpipe-on-node The whole point is to split big task of generating entire HTML into many small tasks ( aka pagelets ). For example assume you want to send to a browser two things: user details and details of a transaction he just made. Instead of doing both these tasks ( ie loading data from DB ) and sending HTML after that, you would load a user from DB an send it to browser, then load transaction from DB and send it to browser.

This ultimetly leads to a better user experience and faster loading time ( because browser generates HTML chunk by chunk which is more efficient then everything at once ).

Note that this was developed ( or at least widely used by ) Facebook, so read this article for more info:

http://www.facebook.com/note.php?note_id=389414033919

RubyLouvre commented 9 years ago

http://stackoverflow.com/questions/25646690/koajs-how-to-send-partial-responses