之前看过的内容,感受忘得差很少,最近抽空又看了一次,果真书读百遍其义自见javascript
Generator函数能够实现函数内外的数据交换和执行权交换。java
从第一次调用next
开始,从函数头部开始执行,执行到第一个yield
语句时,把执行权交出到函数外部,并返回该yield
语句右值,同时在此处暂停函数异步
在下一次调用next
时候(能够传递参数),把执行权返还给函数内部,同时把参数赋值给上一次暂停的yield
语句的左值,并从该行到开始执行到下一个yield
前,并一直循环该过程函数
须要注意的是,yield
语句的左值,不能由右值赋值,如 let a = yield 3
,a
的值并不等于3,a
的只能由函数外部调用next
时传入的参数赋值。ui
function test() { return 3; } function* gen(){ console.log(0); let yield1 = yield 1; console.log('yield1 value: ', yield1);// yield1: 2 let yield2 = yield test(); console.log('yield2 value: ', yield2);// yield2: 4 return 3; } let gen1 = gen(); let next1 = gen1.next(); console.log('next1 value: ', next1);// next: { value: 1, done: false } let next2 = gen1.next(2); console.log('next2 value: ', next2);// next: { value: 3, done: false } let next3 = gen1.next(4); console.log('next3 value: ', next3);// next: { value: undefined, done: true }
console.log(0)
yield1 = yield 1
,此时会把表达式右值返回, 即返回 1
next1 = {value: 1, done: false}
, 接着输出 next1
gen
函数内部在yield1 = yield 1
处暂停 yield1 = yield 1
开始执行2
, 第一次调用已经执行了该yield
语句,因此并不会返回右值,而是会进行赋值操做,把传入的参数 2
赋给 yield1
console.log('yield1 value: ', yield1)
, 此时yield1 = 2
yield2 = yield test()
, 此时会把表达式右值返回, 即返回 3
next2 = {value: 3, done: false}
, 接着输出 next2
gen
函数内部在yield2 = yield test()
处暂停 yield2 = yield test()
开始执行4
, 进行赋值操做,此时yield2 = 4
console.log('yield2 value: ', yield2)
, 此时的 yield2
值为4yield
语句,因此一直执行执行到函数尾部return 5
next3 = {value: 5, done: true}
, 接着输出 next2
咱们发现Generator函数的执行就是一个循环调用next的过程,天然的想到使用递归来实现自动执行
this
function* gen() { let a = yield 1; let b = yield 2; let c = yield 3; } var g = gen(); var res = g.next(); while(!res.done){ console.log(res.value); res = g.next(); }
最简单的几行代码,就实现了Generator的"自动执行",但有一个致命的缺点,代码里若是有一步异步操做,而且下一步的操做依赖上一步的结果才能执行,这样的代码就会出错,没法执行,代码以下code
function* gen() { let file1 = yield fs.readFile('a', () => {}); let file2 = yield fs.readFile(file1.name, () => {}); } var g = gen(); var res = g.next(); // 异步操做,执行file2的yield时 // file1的值为undefined while(!res.done){ res = g.next(res.value); }
这就十分尴尬了...使用Generator的一个初衷就是为了不多层次的回调,写出同步代码
,而咱们如今又卡在了回调上,因此须要使用Thunk函数对象
开发中多数状况都不会单独使用Thunk函数,可是把Thunk和Generator结合在一块儿使用时,就会发生奇妙的化学反应,能够用来实现Generator函数的自动执行。递归
Thunk化用一句话总结就是,将一个具备多个参数且有包含一个回调函数的函数转换成一个只接受回调函数做为参数的单参数函数,附一段网上的实现ip
const Thunk = function(fn) { return function (...args) { return function (callback) { return fn.call(this, ...args, callback); } }; };
具体原理很少赘述,按照我的理解,函数Thunk化,就是把带有回调函数的函数拆分为两步执行
// 普通函数 function func(a, b, callback){ const sum = a + b; callback(sum); } // 普通调用 func(1, 2, alert); // 对函数进行Thunk化 const ft = thunkify(func); // Thunk化函数调用 ft(1, 2)(alert);
包含异步操做的例子,在执行fs.readFile(fileName)
这第一步操做值以后,数据已经拿到,可是不对数据进行操做,而是在第二步的(err, data) => {}
回调函数中进行数据操做
let fs = require('fs'); // 正常版本的readFile fs.readFile(fileName, (err, data) => {}); // Thunk版本的readFile fs.readFile(fileName)((err, data) => {});
目前结合Thunk
和Promise
均可以实现
上面报错的例子,把readFile
Thunk化以后,问题就可以获得解决,
let thunkify = require('thunkify'); let readFileThunk = thunkify(fs.readFile); function* gen() { let file1 = yield readFileThunk('a'); let file2 = yield readFileThunk(file1.name); } var g = gen(); var r1 = g.next(); r1.value(function (err, data) { // 这个回调就是readFileThunk('a')的回调 var r2 = g.next(data); // 等价于file1 = data; r2.value(function (err, data) { if (err) throw err; g.next(data); }); });
执行next
后返回对象中的value
,再也不是一个简单的值,而是一个回调函数,即readFileThunk
的第二步操做,在这个回调函数里,能够取得异步操做的结果,更重要的是能够在这个回调函数中继续调用next
,把函数的执行权返还给gen函数内部,同时把file1的值经过next
的参数传递进去,整个递归就能一直运行。
沿用上面的例子,把readFile
包装成一个Promise对象
const readFile = function (fileName){ return new Promise(function (resolve, reject){ fs.readFile(fileName, function(error, data){ if (error) return reject(error); resolve(data); }); }); }; function* gen() { let file1 = yield readFileThunk('a'); let file2 = yield readFileThunk(file1.name); } var g = gen(); var r1 = g.next(); r1.value.then(function (data) { // 这个回调就是resolve(data) var r2 = g.next(data); // 等价于file1 = data; r2.value.then(function ( data) { if (err) throw err; g.next(data); }); });
经过在then
里执行回调函数,获取到上一步操做的结果和交回执行权,并把值传递回gen函数内部,实现了递归执行
进一步封装,能够获得如下的代码
let Bluebird = require('bluebird'); let readFileThunk = Bluebird(fs.readFile); function run(fn) { const gen = fn(); function next(err, data) { const result = gen.next(data); if (result.done) { result.value; } else { result.value.then((data) => { next(data); }); } } // 递归执行 next(); } run(function* g() { let file1 = yield readFileThunk('a'); let file2 = yield readFileThunk(file1.name); });