广告招人:阿里巴巴招前端,在这里你能够享用大公司的福利和手艺系统,也有小团队的挑战和生长空间。
联络: qingguang.meiqg at alibaba-inc.com
起首请原谅我的题目党(●—●),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 天生器经由过程next
和yield
的合营完成流程掌握,上面的代码实行了三次 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"
参考
《es7-async》 https://github.com/jaydson/es7-async
《Generator 函数的寄义与用法》 http://www.ruanyifeng.com/blog/2015/04/generator.html
《Iterator》 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator