专栏首页我的前端路异步流程控制:7 行代码学会 co 模块

异步流程控制:7 行代码学会 co 模块

首先请原谅我的标题党(●—●),tj 大神的 co 模块源码200多行,显然不是我等屌丝能随便几行代码就能重写的。只是当今大家都喜欢《7天学会xx语言》之类的速效仙丹,于是我也弄个类似的名字《7行代码学会co模块》来博眼球。

为了避免被拖出去弹小JJ,还是先放出所谓的 7 行代码给大家压压惊:

function co(gen) {
    var it = gen();
    var ret = it.next();
    ret.value.then(function(res) {
        it.next(res);
    });
} 

万恶的回调

对前端工程师来说,异步回调是再熟悉不过了,浏览器中的各种交互逻辑都是通过事件回调实现的,前端逻辑越来越复杂,导致回调函数越来越多,同时 nodejs 的流行也让 javascript 在后端的复杂场景中得到应用,在 nodejs 代码中更是经常看到层层嵌套。

以下是一个典型的异步场景:先通过异步请求获取页面数据,然后根据页面数据请求用户信息,最后根据用户信息请求用户的产品列表。过多的回调函数嵌套,使得程序难以维护,发展成万恶的回调

$.get('/api/data', function(data) {
    console.log(data);
    $.get('/api/user', function(user) {
        console.log(user);
        $.get('/api/products', function(products) {
            console.log(products)
        });
    });
});

异步流程控制

  • 最原始异步流程的写法,就是类似上面例子里的回调函数嵌套法,用过的人都知道,那叫一个酸爽。
  • 后来出现了 Promise ,它极大提高了代码的可维护性,消除了万恶的回调嵌套问题,并且现在已经成为 ES6 标准的一部分。
$.get('/api/data')
.then(function(data) {
    console.log(data);
    return $.get('/api/user');
})
.then(function(user) {
    console.log(user);
    return $.get('/api/products');
})
.then(function(products) {
    console.log(products);
});
  • 之后在 nodejs 圈出现了 co 模块,它基于 ES6 的 generator 和 yield ,让我们能用同步的形式编写异步代码。
co(function *() {
    var data = yield $.get('/api/data');
    console.log(data);
    var user = yield $.get('/api/user');
    console.log(user);
    var products = yield $.get('/api/products');
    console.log(products);
});
  • 以上的 Promise 和 generator 最初创造它的本意都不是为了解决异步流程控制。其中 Promise 是一种编程思想,用于“当xx数据准备完毕,then执行xx动作”这样的场景,不只是异步,同步代码也可以用 Promise。而 generator 在 ES6 中是迭代器生成器,被 TJ 创造性的拿来做异步流程控制了。真正的异步解决方案请大家期待 ES7 的 async 吧!本文以下主要介绍 co 模块。

co 模块

上文已经简单介绍了co 模块是能让我们以同步的形式编写异步代码的 nodejs 模块,主要得益于 ES6 的 generator。nodejs >= 0.11 版本可以加 --harmony 参数来体验 ES6 的 generator 特性,iojs 则已经默认开启了 generator 的支持。

要了解 co ,就不得不先简单了解下 ES6 的 generator 和 iterator。

Iterator

Iterator 迭代器是一个对象,知道如何从一个集合一次取出一项,而跟踪它的当前序列所在的位置,它提供了一个next()方法返回序列中的下一个项目。

var lang = { name: 'JavaScript', birthYear: 1995 };
var it = Iterator(lang);
var pair = it.next(); 
console.log(pair); // ["name", "JavaScript"]
pair = it.next(); 
console.log(pair); // ["birthYear", 1995]
pair = it.next(); // A StopIteration exception is thrown

乍一看好像没什么奇特的,不就是一步步的取对象中的 key 和 value 吗,for ... in也能做到,但是把它跟 generator 结合起来就大有用途了。

Generator

Generator 生成器允许你通过写一个可以保存自己状态的的简单函数来定义一个迭代算法。Generator 是一种可以停止并在之后重新进入的函数。生成器的环境(绑定的变量)会在每次执行后被保存,下次进入时可继续使用。generator 字面上是“生成器”的意思,在 ES6 里是迭代器生成器,用于生成一个迭代器对象。

function *gen() {
    yield 'hello';
    yield 'world';
    return true;
}

以上代码定义了一个简单的 generator,看起来就像一个普通的函数,区别是function关键字后面有个*号,函数体内可以使用yield语句进行流程控制。

var iter = gen();
var a = iter.next();
console.log(a); // {value:'hello', done:false}
var b = iter.next();
console.log(b); // {value:'world', done:false}
var c = iter.next();
console.log(c); // {value:true, done:true}

当执行gen()的时候,并不执行 generator 函数体,而是返回一个迭代器。迭代器具有next()方法,每次调用 next() 方法,函数就执行到yield语句的地方。next() 方法返回一个对象,其中value属性表示 yield 关键词后面表达式的值,done 属性表示是否遍历结束。generator 生成器通过nextyield的配合实现流程控制,上面的代码执行了三次 next() ,generator 函数体才执行完毕。

co 模块思路

从上面的例子可以看出,generator 函数体可以停在 yield 语句处,直到下一次执行 next()。co 模块的思路就是利用 generator 的这个特性,将异步操作跟在 yield 后面,当异步操作完成并返回结果后,再触发下一次 next() 。当然,跟在 yield 后面的异步操作需要遵循一定的规范 thunks 和 promises。

yieldables The yieldable objects currently supported are:

  • promises
  • thunks (functions)
  • array (parallel execution)
  • objects (parallel execution)
  • generators (delegation)
  • generator functions (delegation)

7行代码

再看看文章开头的7行代码:

function co(gen) {
    var it = gen();
    var ret = it.next();
    ret.value.then(function(res) {
        it.next(res);
    });
}

首先生成一个迭代器,然后执行一遍 next(),得到的 value 是一个 Promise 对象,Promise.then() 里面再执行 next()。当然这只是一个原理性的演示,很多错误处理和循环调用 next() 的逻辑都没有写出来。

下面做个简单对比: 传统方式,sayhello是一个异步函数,执行helloworld会先输出"world"再输出"hello"

function sayhello() {
    return Promise.resolve('hello').then(function(hello) {
        console.log(hello);
    });
}
function helloworld() {
    sayhello();
    console.log('world');
}
helloworld();

输出

> "world"
> "hello"

co 的方式,会先输出"hello"再输出"world"

function co(gen) {
    var it = gen();
    var ret = it.next();
    ret.value.then(function(res) {
        it.next(res);
    });
}
function sayhello() {
    return Promise.resolve('hello').then(function(hello) {
        console.log(hello);
    });
}
co(function *helloworld() {
    yield sayhello();
    console.log('world');
});

输出

> "hello"
> "world"

消除回调金字塔

假设sayhello/sayworld/saybye是三个异步函数,用真正的 co 模块就可以这么写:

var co = require('co');
co(function *() {
    yield sayhello();
    yield sayworld();
    yield saybye();
});

输出

>"hello"

>"world"

>"bye"

资料:

《value》:www.smpeizi.com

《co 模块思路》:www.aiidol.com

《generator》:www.3sjtw.com

《Iterator》:www.idiancai.com

原文链接:http://www.yyyweb.com/4641.html

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 深入理解JavaScript系列(46):代码复用模式(推荐篇)

    同时,ECMAScript5也提供了类似的一个方法叫做Object.create用于继承对象,用法如下:

    用户4962466
  • 深入理解JavaScript系列(47):对象创建模式(上篇)

    本篇主要是介绍创建对象方面的模式,利用各种技巧可以极大地避免了错误或者可以编写出非常精简的代码。

    用户4962466
  • 深入理解JavaScript系列(32):设计模式之观察者模式

    观察者模式又叫发布订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时...

    用户4962466
  • 前端MVC Vue2学习总结(七)——ES6与Module模块化、Vue-cli脚手架搭建、开发、发布项目与综合示例

    使用vue-cli可以规范项目,提高开发效率,但是使用vue-cli时需要一些ECMAScript6的知识,特别是ES6中的模块管理内容,本章先介绍ES6中的基...

    张果
  • 前端MVC Vue2学习总结(七)——ES6与Module模块化、Vue-cli脚手架搭建、开发、发布项目与综合示例

    使用vue-cli可以规范项目,提高开发效率,但是使用vue-cli时需要一些ECMAScript6的知识,特别是ES6中的模块管理内容,本章先介绍ES6中的基...

    张果
  • js程序设计02——变量、作用域问题

    首先,ECMAScript中的数据类型分为基本类型、引用类型,基本类型的访问操作是按值的。引用类型的值是保存在内存中的对象,操作对象时,实际上操作的是对象的引用...

    用户1141560
  • 37个JavaScript基本面试问题和解答(建议收藏)

    1、使用typeof bar ===“object”来确定bar是否是一个对象时有什么潜在的缺陷?这个陷阱如何避免?

    用户1272076
  • 探究{ a = 1; function a(){} }和{ function b(){}; b = 1 }

    相信大部分人都了解了,这里再重复啰嗦一下。js是解析执行的,变量提升是js中执行上下文的工作方式。变量声明和函数声明在编译阶段会被提前。

    lhyt
  • Javascript一些优雅实现

    卡少
  • js线程机制的介绍和练习

    卡少

扫码关注云+社区

领取腾讯云代金券