搞定 Koa 之 Nodejs generator 与 co
搞定 Koa 之 Nodejs generator 与 co
koa 是由 tj大神利用 generator 开发的 web 框架。要理解 koa,首先要先了解 generator 与 co。作为搞定 koa 的第一篇,我们便谈谈这个。文章首发在boke.io
系列目录
generator介绍
function* Gene(){
yield 1;
yield 2;
}
var gene = Gene();
console.log(gene.next());//{ value: 1, done: false }
console.log(gene.next());//{ value: 2, done: false }
console.log(gene.next());//{ value: undefined, done: true }
console.log(gene.next());//Error: Generator has already finished 经[@Ralph-Wang](/user/Ralph-Wang)提醒从 v0.11.13 开始不抛错了,返回{ value: undefined, done: true }
从上面我们可以看到
- generator 的定义和函数类似,只是在 function 后面多了一个*
- 调用generator 和调用函数一样,只是不像函数立即执行,而是会生成一个对象
- generator 生成的对象存在一个 next 函数,调用 next 会返回 yield运算的结果对象,并停止。再次调用会在下一个 yield 处停止。
- 当所有的 yield 被执行完,调用 next 函数会返回{ value: undefined, done: true }。再次调用会报错
generator 与异步
看完 generator 的介绍,你心里回想这跟异步有毛关系?不着急听我接着说
串行请求两个网页的代码
var request = require('request');
var a = {};
var b = {};
request('http://www.google.com', function (error, response, body) {
if (!error && response.statusCode == 200) {
a.response = response;
a.body = body;
request('http://www.yahoo.com', function (error, response, body) {
if (!error && response.statusCode == 200) {
b.response = response;
b.body = body;
}
});
}
});
我们再看看最终我们是如何利用 generator请求网页的
co(function *(){
var a = yield request('http://google.com');
var b = yield request('http://yahoo.com');
console.log(a[0].statusCode);
console.log(b[0].statusCode);
})()
上面的代码可以看到,co 里面传入了一个 generator,里面用 yield 调用异步函数。从逻辑上看,里面的异步被变成了同步。
co 到底有什么魔法?可以把我们从异步中解救出来?
想想 generator 的特性,当我们执行第一个 next 的时候,会调用第一个 request,此时我们去调用 request 的逻辑,然后把 generator 的实例穿进去。当第一个 request 逻辑完成时,在调用这个 generator 的 next,这样就到了第二个 yield 的逻辑了。当然这需要一些逻辑的封装,也就是 co 了。
根据上面的分析,我们大概可以写出下面的代码
function co(Gene){
//先实例化一下
var gene = Gene();
//如果存在 next 函数
if(gene.next){
var fun = gene.next();//把异步函数返回过来,好继续封装
//fun 处理完,再调用 gene.next()
//...
}
}
从上面的代码可以看出
- yield 后面的内容需要返回一个异步函数,这样我们才可进一步封装异步处理的逻辑。
- fun 需要可以传入参数,这样才可以处理多个异步
- 需要一个递归调用,这样才可以持续调用 next,同时根据 next 返回对象中的done属性停止逻辑
- 需要考虑错误处理
今天就到这里,下一篇详细讲一下 co 的源码
参考资料
解决回调金字塔! Harmony Generator, yield, ES6, co框架学习
联系我
搞定 Koa 之 Node.js generator 与 co
koa 是由 TJ 大神利用 generator 开发的 Web 框架。要理解 koa,首先要先了解 generator 与 co。作为搞定 koa 的第一篇,我们便谈谈这个。文章首发在 boke.io。
系列目录
generator 介绍
首先来看一个简单的 generator 函数示例:
function* Gene() {
yield 1;
yield 2;
}
var gene = Gene();
console.log(gene.next()); // { value: 1, done: false }
console.log(gene.next()); // { value: 2, done: false }
console.log(gene.next()); // { value: undefined, done: true }
从上面我们可以看到:
- generator 的定义和普通函数类似,只是在
function
后面多了一个*
。 - 调用 generator 和调用函数一样,只是不会立即执行,而是会生成一个对象。
- generator 生成的对象存在一个
next
函数,调用next
会返回yield
运算的结果对象,并停止。再次调用会在下一个yield
处停止。 - 当所有的
yield
被执行完,调用next
函数会返回{ value: undefined, done: true }
。再次调用会报错。
generator 与异步
看完 generator 的介绍,你可能在想这和异步有什么关系?其实,generator 可以帮助我们更好地处理异步操作。
传统异步代码
假设我们需要串行请求两个网页:
var request = require('request');
var a = {};
var b = {};
request('http://www.google.com', function (error, response, body) {
if (!error && response.statusCode == 200) {
a.response = response;
a.body = body;
request('http://www.yahoo.com', function (error, response, body) {
if (!error && response.statusCode == 200) {
b.response = response;
b.body = body;
}
});
}
});
这种嵌套的回调地狱(callback hell)使得代码难以阅读和维护。
使用 generator 和 co 处理异步
我们可以使用 generator 和 co 来简化异步操作:
const co = require('co');
const request = require('request');
co(function *() {
var a = yield request('http://google.com');
var b = yield request('http://yahoo.com');
console.log(a[0].statusCode); // 打印 Google 的状态码
console.log(b[0].statusCode); // 打印 Yahoo 的状态码
})();
在这个例子中,co
函数接收一个 generator 函数,并自动管理异步操作。yield
关键字后面的内容必须是一个可以 then
的 Promise 对象或类似的异步函数。co
会处理这些异步操作并按顺序执行。
co 的工作原理
co
的核心逻辑是这样的:
function co(gen) {
const it = gen();
return new Promise((resolve, reject) => {
function step(nextF) {
let next;
try {
next = nextF();
} catch(e) {
return reject(e);
}
if (next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(
res => step(() => it.next(res)),
err => step(() => it.throw(err))
);
}
step(() => it.next());
});
}
这段代码实现了:
- 实例化 generator 并调用
next()
。 - 检查是否已经完成 (
done
)。 - 将
yield
后面的值转换为 Promise,并处理结果。 - 递归调用
step
函数,直到所有yield
完成。
通过这种方式,我们可以将复杂的异步操作变得像同步代码一样简洁易读。
总结
本文介绍了 generator 的基本概念以及如何利用 generator 和 co 来处理异步操作。希望这些知识能帮助你更好地理解和使用 koa 框架。
参考资料
联系我
node 0.11.14.
function* Gene(){
yield 1;
yield 2;
}
var gene = Gene();
console.log(gene.next());//{ value: 1, done: false }
console.log(gene.next());//{ value: 2, done: false }
console.log(gene.next());//{ value: undefined, done: true }
console.log(gene.next());//{ value: undefined, done: true }
mark
在理解 Koa 框架之前,我们需要了解 generator
与 co
的基本概念和使用方法。generator
是 ES6 引入的一种特殊函数,可以暂停和恢复执行流程。而 co
是一个工具库,用于简化 generator
中的异步操作。
generator 简介
generator
函数通过在 function
关键字后面添加 *
来定义。它可以通过 yield
关键字来暂停执行并返回中间值。每次调用 next()
方法都会恢复执行,直到遇到下一个 yield
。
function* Gene() {
yield 1;
yield 2;
}
const gene = Gene();
console.log(gene.next()); // { value: 1, done: false }
console.log(gene.next()); // { value: 2, done: false }
console.log(gene.next()); // { value: undefined, done: true }
generator 与异步操作
generator
本身并不直接支持异步操作,但结合 co
可以实现将异步操作转换为看起来像同步的代码。co
通过包装 generator
函数,使其能够处理异步操作并按顺序执行。
例子
假设我们有两个异步请求:
co(function* () {
const responseA = yield request('http://www.google.com');
const responseB = yield request('http://www.yahoo.com');
console.log(responseA[0].statusCode); // 打印 Google 的响应状态码
console.log(responseB[0].statusCode); // 打印 Yahoo 的响应状态码
})();
co 库的基本实现
为了更好地理解 co
的工作原理,我们简要地实现一个简单的版本:
function co(generatorFn) {
const generator = generatorFn();
function handleNext(value) {
const result = generator.next(value);
if (!result.done) {
result.value.then(handleNext, handleNext);
}
}
handleNext();
}
解释
- 初始化:首先创建
generator
实例。 - 处理
next
:定义一个递归函数handleNext
,用来处理generator
的next
调用。 - 递归调用:每次调用
handleNext
时,会检查当前是否已经完成。如果没有完成,则将异步操作的结果作为参数再次调用handleNext
。
通过这种方式,co
库可以使异步代码看起来像同步代码一样运行,从而减少回调地狱,使代码更易读和维护。