对前端工程师来讲,异步回调是再熟悉不过了,浏览器中的各类交互逻辑都是经过事件回调实现的,前端逻辑愈来愈复杂,致使回调函数愈来愈多,同时 nodejs 的流行也让 javascript 在后端的复杂场景中获得应用,在 nodejs 代码中更是常常看到层层嵌套。javascript
如下是一个典型的异步场景:先经过异步请求获取页面数据,而后根据页面数据请求用户信息,最后根据用户信息请求用户的产品列表。过多的回调函数嵌套,使得程序难以维护,发展成万恶的回调。php
$.get('/api/data', function(data) { console.log(data); $.get('/api/user', function(user) { console.log(user); $.get('/api/products', function(products) { console.log(products) }); }); });
最原始异步流程的写法,就是相似上面例子里的回调函数嵌套法,用过的人都知道,那叫一个酸爽。html
后来出现了 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); });
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); });
上文已经简单介绍了co 模块是能让咱们以同步的形式编写异步代码的 nodejs 模块,主要得益于 ES6 的 generator。nodejs >= 0.11 版本能够加 --harmony
参数来体验 ES6 的 generator 特性,iojs 则已经默认开启了 generator 的支持。java
要了解 co ,就不得不先简单了解下 ES6 的 generator 和 iterator。node
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 字面上是“生成器”的意思,在 ES6 里是迭代器生成器,用于生成一个迭代器对象。后端
function *gen() { yield 'hello'; yield 'world'; return true; }
以上代码定义了一个简单的 generator,看起来就像一个普通的函数,区别是function
关键字后面有个*
号,函数体内可使用yield
语句进行流程控制。api
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 生成器经过next
和yield
的配合实现流程控制,上面的代码执行了三次 next() ,generator 函数体才执行完毕。
从上面的例子能够看出,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行代码:
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"
转载自:https://www.cnblogs.com/yelongsan/p/6369474.html