开源软件名称: dead-horse/co-and-koa-talk开源软件地址: https://github.com/dead-horse/co-and-koa-talk开源编程语言:
JavaScript
100.0%
开源软件介绍: title: Generator, Co and Koa
author:
name: dead_horse
url: https://github.com/dead-horse
output: index.html
controls: false
--
--
generator
ES6 新特性
node v0.11 可以使用 (node --harmony)
通过 gnode 体验
--
function* hello() {
yield 'hello';
yield function () {
return 'generator';
};
return 'done';
}
> hello.constructor.name // GeneratorFunction
> var gen = hello();
> typeof gen.next === 'funciton';
> typeof gen.throw === 'function';
> var res = gen.next();
> res.value === 'hello';
> res = gen.next();
> res.value() === 'generator';
> res = gen.next();
> res.value === 'done';
> res.done = true;
--
function* fibonacci(total) {
var first = 0;
var second = 1;
var tmp;
yield first;
yield second;
while(total--) {
tmp = first;
first = second;
second = tmp + second;
yield second;
}
}
--
generator 与异步
--
yield 异步方法
function delay(done) {
setTimeout(function () {
done(null, 'delay done');
}, 1000);
}
function *genFn() {
console.log('before delay');
console.log(yield delay);
console.log('after delay');
}
var gen = genFn();
next();
function next(err, res) {
var ret = gen.next(res);
if (!ret.done) {
return ret.value(next);
}
}
full example
--
yieldable and thunk
只接受一个参数(且为callback)的异步函数
需要将所有的 callback 形式的方法都转换成 thunk
function delay(interval) {
// 返回的是一个 thunk
return function (done) {
setTimeout(function () {
done(null, 'delay done');
}, interval);
};
}
--
callback to thunk
fs.stat(filename, callback);
// =>
function stat(filename) {
return function (done) {
fs.stat(filename, done);
}
}
// =>
function *() {
yield stat('./README.md');
}
--
promise to thunk
function (promise) {
return function (fn) {
promise.then(funciton (res) {
fn(null, res);
}, fn);
}
}
--
function arrayToThunk(array) {
return function (done) {
var called = false, len = array.length, result = [];
// 同时并发的执行 array 里面的异步函数
// 异步函数执行完调用 cb
// cb 调用 `array.lenth` 次之后执行 done
// 这样把一个函数的数组转换成了一个 thunk
array.forEach(function (fn, index) {
fn(function (err, data) {
if (err) return cb(err);
result[index] = data;
cb();
});
});
// cb 执行 `len` 次之后才会执行 done()
var cb = function (err) {
if (called) return;
if (err) {
called = true;
return done(err);
}
if (--len === 0) {
called = true;
done(null, result);
}
};
};
}
--
var thunkify = require('thunkify');
var co = require('co');
var fs = require('fs');
var stat = thunkify(fs.stat);
var readFile = thunkify(fs.readFile);
co(function *() {
var stat = yield stat('./README.md');
var content = yield readFile('./README.md');
})();
--
原理
可以被 yield 的有: thunk, promise, generator, generatorFunction, object, array
所有的 node 形式的 callback 都需要转换成 thunk
object
,array
,promise
自动识别并转换成 thunk
generator
, generatorFunction
自动识别并展开执行
--
series and parallel
co(function *() {
var a = yield request(a);
var b = yield request(b);
})();
co(function *() {
var res = yield [request(a), request(b)];
})();
--
基于 co 的流程控制
--
把 generator 代码编译成 ES5 的代码
可以实现基于 generator 的编写的库的向下兼容
但是需要保证所有的依赖(dependencies) 都是向下兼容(非 generator 或者支持转换)的
Examples
--
TJ 和 express 团队的新作品
基于 generator 和 co 的异步解决方案
setter / getter 带来了更方便的 http 辅助方法
更人性化的错误处理
更灵活的中间件形式
--
koa VS express
--
Hello world
var koa = require('koa');
var app = koa();
app.use(function *() {
this.body = 'hello!';
});
app.listen(7001);
--
中间件
--
function *responseTime() {
var start = Date.now();
yield next;
var use = Date.now() - start;
this.set('X-ResponseTime', use);
}
更灵活的中间件形式
var koa = require('koa');
var ejs = require('koa-ejs');
var app = koa();
ejs(app, {/*options*/});
// 挂载 function* render() {} 在 context.prototype上
app.use(function* () {
return yield this.render('index');
});
--
更简洁的中间件实现方式
--
koa 中的异步
var fs = require('co-fs');
app.use(function *(){
var paths = yield fs.readdir('docs');
// parallel
var files = yield paths.map(function(path){
return fs.readFile('docs/' + path, 'utf8');
});
this.type = 'markdown';
this.body = files.join('');
});
--
异常处理
通过 try catch
来捕获所有的异常
所有 throw 出去的 error 都会被 koa 捕获到
app.use(function *() {
try {
var file = yield fs.readFile('./README.md');
} catch (err) {
if (err.code === 'ENOENT') {
this.status = 404;
this.body = 'can not found readme'
return;
};
throw err;
}
});
--
Stream 的异常处理
原生 http server 和 express 中
http.createServer(function (req, res) {
var stream = fs.createReadStream('filename.txt');
stream
.on('error', onerror)
.pipe(zlib.createGzip())
.on('error', onerror)
.pipe(res);
function onerror(err) {
res.statusCode = 500;
res.end(err.message);
console.error(err.stack);
}
res.once('close', function () {
// 如果客户端终止了这个响应,可能导致 `fd` 泄漏, 需要 `unpipe` 来让它关闭这个 `fd`
stream.unpipe();
});
});
--
koa 中通过神奇的 this.body=
帮你处理 Stream 的各种坑
不用担心 fd leak
不用负责监听 error
app.use(function *() {
this.body = fs.createReadStream('filename.txt');
this.body = this.body.pipe(zlib.createGzip());
});
--
深入理解 koa 福利
--
基于 koa 的应用
cnpmjs.org - Private npm registry and web for Enterprise, base on koa, MySQL and Simple Store Service.
simgr - Image proxy and resizing server
component-crawler - component.json
crawler
--
更多中间件
--
connect / experss 向 koa 迁移
框架配置和中间件替换
model(proxy) 层通过 thunkify
或者 thunkify-wrap
包装
改写 controllers
[进一步改写 model 层]
--
基于 co 的单元测试
mocha 开启选项: --harmony
mocha 添加依赖:--require co-mocha
$ mocha --harmony --require co-mocha
it('should co work fine', function *() {
var stat = yield fs.stat('./README.md');
var content = yield fs.readFile('./README.md');
assert(stat.size === content.length);
});
--
推荐关注
--
Q & A
请发表评论