生成器函数
生成器(Generator)函数写成:
function* func () {}
`</pre>
其本质也是一个函数, 所以他具备普通函数所具有的所有特性, 除此以外, 他还具有以下特性
执行生成器函数后返回一个迭代器(Iterator)
生成器函数内部可以使用 yield 或 yield*, 函数执行到 yield 的时候会暂停执行, 并返回 yield 后面表达式的值, 通过迭代器的 next() 方法返回一个对象, 该对象由 value 和 done 属性组成, 其中, value 属性值为 yield 后面表达式的值, done 属性表示生成器的执行状态
迭代器 next() 方法的参数是 yield 表达式的值
当 next 指向最后一个 yield 的时候, 再次执行 next() 方法, 返回的对象中 done: true, 而 value 的值为 return 后面的表达式(默认为 undefined)
例一
`function test() { return 'b'; } function* func() { var a = yield 'a'; console.log('gen', a); // 'gen: undefined' - (如果 next 方法不带参数, yield 表达式返回 undefined) var b = yield test(); console.log('gen': b); // 'gen: undefined' } var func1 = func(); var a = func1.next(); console.log('next: ' a); // 'next: {value: 'a', done: false}' var b = func1.next(); console.log('next: ' b); // 'next: {value: 'b', done: false}' var c = func1.next(); console.log('next: ' c); // 'next: {value: undefined, done: true}' `
关于 yield*
yield 暂停执行并返回后面表达式的值, 而 yield* 则将函数委托到另一个迭代器或可迭代对象
arguments
`> function* genFunc(){ ... yield arguments; ... yield* arguments; ... } undefined > var gen = genFunc(1,2); undefined > gen.next() { value: { '0': 1, '1': 2 }, done: false } > gen.next() { value: 1, done: false } > gen.next() { value: 2, done: false } > gen.next() { value: undefined, done: true } `
Generator
`> function * gen1(){ ... yield 2; ... yield 3; ... } undefined > function * gen2(){ ... yield 1; ... yield* gen1(); ... yield 4; ... } undefined > var g2 = gen2(); undefined > g2.next() { value: 1, done: false } > g2.next() { value: 2, done: false } > g2.next() { value: 3, done: false } > g2.next() { value: 4, done: false } > g2.next() { value: undefined, done: true } `
Thunk 函数
在 co 的应用中, 为了能像同步代码那样书写异步代码, 比较多的使用方式是使用 thunk 函数(但不是唯一方式, 还可以是 Promise), 比如读取文件内容的异步函数 fs.readFile() 方法, 转化为 thunk 函数的方式如下
`function readFile(path, encoding){ return function(cb){ fs.readFile(path, encoding, cb); } } `
Thunk 函数具备以下两个要素
1. 有且只有一个参数是 callback 的函数
2. callback 的第一个参数是 error使用 thunk 函数, 同时结合 co 我们就可以像书写同步代码一样书写异步代码
`var co = require('co'); var fs = require('fs'); var Promise = require('es6-promise').Promise; function readFile(path, encoding){ return function(cb) { // thunk function fs.readFile(path, encoding, cd); }; } co(function* () { // 外部不可见, 但在 co 内部其实已经转化成promise.then().catch()...链式调用的行驶 var a = yield readFile('a.txt', {encoding: 'utf-8'}); console.log(a); // a var b = yield readFile('b.txt', {encoding: 'utf-8'}); console.log(b); // b return yield Promise.resolve(a+b); }).then(function(val){ console.log(val) // ab }).catch(function(error){ console.log(error); }) `
有一个框架 thunkify 可以帮我们轻松实现以上步骤
`var co = require('co'); var thunkify = require('thunkify'); var fs = requrie('fs'); var Promise = require('es6-promise').Promise; var readFile = thunkify(fs.readFile); co(function* () { // 外部不可见, 但在 co 内部已经转化为 promise 的链式调用 var a = yield readFile('a.txt', {encoding: 'utf-8'}); console.log(a); // a var b = yield readFile('b.txt', {encoding: 'utf-8'}); console.log(b); // b return yield Promise.resolve(a+b); }).then(function(val){ console.log(val); // ab }).catch(function(err){ console.log(err); }) `
对于 thunkify 框架的理解注释如下:
`/** * Module dependencies */ var assert = require('assert'); /** * Expose `thunkify()`. */ module.exports = thunkify; function thunkify(fn) { assert('function' == typeof fn, 'function required'); // 返回一个包含 thunk 函数的函数, 返回的 thunk 函数用于执行yield, 而外层的这个函数用于给 thunk 函数传递其他参数 return function() { var args = new Array(arguments.length); // 缓存上下文环境, 给 fn 提供执行环境 var ctx = this; // 将参数类数组对象转为数组(实现方式略臃肿, 可以直接用Array.from) for (var i = 0; i < args.length; ++i){ args[i] = argumetns[i]; } // 真正的 thunk 函数(有且只有一个参数, 并且参数是第一个参数为 err 的 callback) return function(done) { // 返回 thunk 函数 var called; // 将回调函数再包裹一层, 避免重复调用, 同时, 将包裹了的真正的回调函数 push 进参数数组(即将回调函数 done 作为最后一个参数传递给 fn) args.push(function(){ // 将回调函数 done 加到参数列表尾部, 形成 fn 需要的 [arg1, ..., done] if (called) return; called = true; done.apply(null, arguments); // 执行回调函数 }); try { // 在 ctx 环境执行 fn // 并将执行 thunkify 之后返回的函数的参数(含 done 回调) 传入, 类似于执行: // fs.readFile(path, {encoding: 'utf-8'}, done) fn.apply(ctx,args); // 类似 fn(arg1, .., done) } catch(err) { done(err); } } } }