搞定 Koa 之 Nodejs generator 与 co

搞定 Koa 之 Nodejs generator 与 co

koa 是由 tj大神利用 generator 开发的 web 框架。要理解 koa,首先要先了解 generator 与 co。作为搞定 koa 的第一篇,我们便谈谈这个。文章首发在boke.io

系列目录

  1. 搞定 koa 之generator 与 co
  2. 搞定 koa 之 co源码解析

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 的源码

参考资料

学习ES6生成器(Generator)

解决回调金字塔! Harmony Generator, yield, ES6, co框架学习

联系我

微博 GitHub


4 回复

搞定 Koa 之 Node.js generator 与 co

koa 是由 TJ 大神利用 generator 开发的 Web 框架。要理解 koa,首先要先了解 generator 与 co。作为搞定 koa 的第一篇,我们便谈谈这个。文章首发在 boke.io

系列目录

  1. 搞定 koa 之 generator 与 co
  2. 搞定 koa 之 co 源码解析

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 }

在理解 Koa 框架之前,我们需要了解 generatorco 的基本概念和使用方法。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();
}

解释

  1. 初始化:首先创建 generator 实例。
  2. 处理 next:定义一个递归函数 handleNext,用来处理 generatornext 调用。
  3. 递归调用:每次调用 handleNext 时,会检查当前是否已经完成。如果没有完成,则将异步操作的结果作为参数再次调用 handleNext

通过这种方式,co 库可以使异步代码看起来像同步代码一样运行,从而减少回调地狱,使代码更易读和维护。

回到顶部