翻译自html
githubgit
咱们能够把generators理解成一段能够暂停并从新开始执行的函数
es6
function* genFunc() { // (A) console.log('First'); yield; //(B) console.log('Second'); //(C) }
function*是定义generator函数的关键字,yield是一个操做符,generator 能够经过yield暂停本身执行,另外,generator能够经过yield接受输入和对外输入
github
当咱们调用genFunc(),咱们获得一个generator对象genObj,咱们能够经过这个genObj控制程序的执行算法
const genObj = genFunc()
上面的程序初始会暂停在行A
,调用genObj.next()会使程序继续执行直到遇到下一个yieldjson
> genObj.next(); First { value: undefined, done: false }
这里先忽略genObj.next()返回的对象,以后会介绍promise
如今,程序暂停在了行B,再次调用 genObj.next(),程序又开始执行,行C被执行app
> genObj.next() Second { value: undefined, done: true }
而后,函数就执行结束了,再次调用genObj.next()也不会有什么效果了异步
generators 能够扮演三种角色函数
每个yield能够经过next()返回一个值,这意味着generators能够经过循环或递归生产一系列的值,由于generator对象实现了Iterable接口,generator生产的一系列值能够被ES6中任意支持可迭代对象的结构处理
,两个例子,for of循环和扩展操做(...)
yield能够经过next()接受一个值,这意味着generator变成了一个暂停执行的数据消费者直到经过next()给generator传递了一个新值
考虑到generators是能够暂停的而且能够同时做为数据生产者和消费者,不会作太多的工做就能够把generator转变成协做程序(合做进行的多任务)
下面详细介绍这三种
generators同时实现了接口Iterable 和 Iterator(以下所示),这意味着,generator函数返回的对象是一个迭代器也是一个可迭代的对象
interface Iterable { [Symbol.iterator]() : Iterator; } interface Iterator { next() : IteratorResult; } interface IteratorResult { value : any; done : boolean; }
generator对象完整的接口后面会提到,这里删掉了接口Iterable的return()方法,由于这个方法这一小节用不到
generator函数经过yield生产一系列的值,这些值能够经过迭代器的next()方法来使用,例以下面的generator函数生成了值a和b
function* genFunc(){ yield 'a' yield 'b' }
交互展现以下
> const genObj = genFunc(); > genObj.next() { value: 'a', done: false } > genObj.next() { value: 'b', done: false } > genObj.next() // done: true => end of sequence { value: undefined, done: true }
for (const x of genFunc()) { console.log(x); } // Output: // a // b
const arr = [...genFunc()]; // ['a', 'b']
> const [x, y] = genFunc(); > x 'a' > y 'b'
上面的generator函数没有包含一个显式的return,一个隐式的return 返回undefined,让咱们试验一个显式返回return的generator
function* genFuncWithReturn() { yield 'a'; yield 'b'; return 'result'; }
下面的结构代表return 指定的值保存在最后一个next()返回的对象中
> const genObjWithReturn = genFuncWithReturn(); > genObjWithReturn.next() { value: 'a', done: false } > genObjWithReturn.next() { value: 'b', done: false } > genObjWithReturn.next() { value: 'result', done: true }
然而,大部分和可迭代对象一块儿工做的结构会忽略done属性是true的对象的value值
for (const x of genFuncWithReturn()) { console.log(x); } // Output: // a // b const arr = [...genFuncWithReturn()]; // ['a', 'b']
yield*会考虑done属性为true的value值,后面会介绍
若是一个异常离开了generator函数,next()能够抛出它
function* genFunc() { throw new Error('Problem!'); } const genObj = genFunc(); genObj.next(); // Error: Problem!
这意味着next()能够生产三种类型的值
咱们只能在generator函数中使用yield,若是咱们想经过generator实现递归算法,咱们就须要一种方式来在一个generator中调用另外一个generator,这就用到了yield*,如今,咱们只介绍yield*用在generator函数产生值的状况,以后介绍yield*用在generator接受值的状况
generator递归调用另外一个generator的方式
function* foo() { yield 'a'; yield 'b'; } function* bar() { yield 'x'; yield* foo(); yield 'y'; }
执行结构
const arr = [...bar()]; //['x', 'a', 'b', 'y']
在内部,yield*像下面这样工做的
function* bar() { yield 'x'; for (const value of foo()) { yield value; } yield 'y'; }
另外,yield*的操做数不必定非得是一个generator函数生成的对象,能够是任何可迭代的
function* bla() { yield 'sequence'; yield* ['of', 'yielded']; yield 'values'; } const arr = [...bla()]; // ['sequence', 'of', 'yielded', 'values']
yield*考虑可迭代对象的最后一个值
ES6中的不少结构会忽略generator函数返回的可迭代对象的最后一个值(例如 for of,扩展操做符,如上面介绍过的那样),可是,yield*的结果是这个值
function* genFuncWithReturn() { yield 'a'; yield 'b'; return 'The result'; } function* logReturned(genObj) { const result = yield* genObj; console.log(result); // (A) }
执行结果
> [...logReturned(genFuncWithReturn())] The result [ 'a', 'b' ]
做为数据的消费者,generator函数返回的对象也实现了接口Observer
interface Observer { next(value? : any) : void; return(value? : any) : void; throw(error) : void; }
做为observer,generator暂停执行直到它接受到输入值
,这有三种类型的输入,经过如下三种observer接口提供的方法
function* dataConsumer() { console.log('Started'); console.log(`1. ${yield}`); // (A) console.log(`2. ${yield}`); return 'result'; }
首先获得generator对象
const genObj = dataConsumer();
而后执行genObj.next(),这会开始这个generator.执行到第一个yield处而后暂停。此时next()的结果是yield在行A产出的值(是undifined,由于这地方的yield后面没有操做数
)
> genObj.next() //Started { value: undefined, done: false }
而后再调用next()两次,第一次传个参数'a',第二次传参数'b'
> genObj.next('a') //1. a { value: undefined, done: false } > genObj.next('b') //2. b { value: 'result', done: true }
能够看到,第一个next()调用的做用仅仅是开始这个generator,只是为了后面的输入作准备
能够封装一下
function coroutine(generatorFunction) { return function (...args) { const generatorObject = generatorFunction(...args); generatorObject.next(); return generatorObject; }; }
使用
const wrapped = coroutine(function* () { console.log(`First input: ${yield}`); return 'DONE'; }); > wrapped().next('hello!') First input: hello!
generator对象有两个另外的方法,return()和throw(),和next()相似
让咱们回顾一下next()是怎么工做的:
继续执行到下一个yield,return或者throw:
return()和throw() 和next()相似工做,但在第二步有所不一样
return()终止generator
return() 在 yield的位置执行return
function* genFunc1() { try { console.log('Started'); yield; // (A) } finally { console.log('Exiting'); } } > const genObj1 = genFunc1(); > genObj1.next() Started { value: undefined, done: false } > genObj1.return('Result') Exiting { value: 'Result', done: true }
阻止终止
咱们能够阻止return()终止generator若是yield是在finally块内(或者在finally中使用return语句)
function* genFunc2() { try { console.log('Started'); yield; } finally { yield 'Not done, yet!'; } }
这一次,return()没有退出generator函数,固然,return()返回的对象的done属性就是false
> const genObj2 = genFunc2(); > genObj2.next() Started { value: undefined, done: false } > genObj2.return('Result') { value: 'Not done, yet!', done: false }
能够再执行一次next()
> genObj2.next() { value: 'Result', done: true }
发送一个错误
throw()在yield的位置抛一个异常
function* genFunc1() { try { console.log('Started'); yield; // (A) } catch (error) { console.log('Caught: ' + error); } }
> const genObj1 = genFunc1(); > genObj1.next() Started { value: undefined, done: false } > genObj1.throw(new Error('Problem!')) Caught: Error: Problem! { value: undefined, done: true }
到目前为止,咱们只看到以yield的一个层面: 它传播生成的值从被调用者到调用者。既然咱们如今对generator接受值感兴趣,咱们就来看一下yield的另外一个层面:yield*能够发送调用者接受的值给被调用者。在某种程度上,被调用者变成了活跃的generator,它能够被调用者生成的对象控制
function* callee() { console.log('callee: ' + (yield)); } function* caller() { while (true) { yield* callee(); } }
> const callerObj = caller(); > callerObj.next() // start { value: undefined, done: false } > callerObj.next('a') callee: a { value: undefined, done: false } > callerObj.next('b') callee: b { value: undefined, done: false }
这一节介绍generator完整的接口(组合做为数据生产者和消费者两种角色)和一个同时要使用这两种角色的使用场景:协同操做多任务
interface Generator { next(value? : any) : IteratorResult; throw(value? : any) : IteratorResult; return(value? : any) : IteratorResult; } interface IteratorResult { value : any; done : boolean; }
接口Generator结合了咱们以前介绍过的两个接口:输出的Iterator和输入的Observer
interface Iterator { // data producer next() : IteratorResult; return?(value? : any) : IteratorResult; } interface Observer { // data consumer next(value? : any) : void; return(value? : any) : void; throw(error) : void; }
合做多任务是咱们须要generators同时处理输入和输出,在介绍generator是如何工做的以前,让咱们先复习一下JavaScript当前的并行状态
js是单线程的,但有两种方式能够消除这种限制
经过generators来简化异步操做
一些基于Promise的库经过generator来简化了异步代码,generators做为Promise的客户是很是理想的,由于它们能够暂停直到结果返回
下面的例子代表co是如何工做的
co(function* () { try { const [croftStr, bondStr] = yield Promise.all([ // (A) getFile('http://localhost:8000/croft.json'), getFile('http://localhost:8000/bond.json'), ]); const croftJson = JSON.parse(croftStr); const bondJson = JSON.parse(bondStr); console.log(croftJson); console.log(bondJson); } catch (e) { console.log('Failure to read: ' + e); } });
注意这段代码看起来是多么的同步啊,虽然它在行A处执行了一个异步调用。
使用generators对co的一个简单的实现
function co(genFunc) { const genObj = genFunc(); step(genObj.next()); function step({value,done}) { if (!done) { // A Promise was yielded value .then(result => { step(genObj.next(result)); // (A) }) .catch(error => { step(genObj.throw(error)); // (B) }); } } }
这里忽略了next()(行A)和throw()(行B)能够回抛异常
借助上面的使用分析一下:
首先获得generator对象
const genObj = genFunc();
而后将genObj.next()的返回值传递给step方法
step()中获取到value和done,若是generator没有执行完,当前的value就是上面使用中定义的promise
等到promise执行完,而后将结果result传递给generator函数
genObj.next(result) 而后在generator中程序继续往下执行 const [croftStr, bondStr] = yield XXXX . . . .
注意行A处递归调用step(genObj.next(result)),使得generator函数中能够存在多个异步调用,而co都能处理
整个过程多么的巧妙啊。。。。。。。。。