从C#到TypeScript - Generator

从C#到TypeScript - Generator

上篇讲了PromisePromise的执行须要不停的调用then,虽然比callback要好些,但也显得累赘。因此ES6里添加了Generator来作流程控制,能够更直观的执行Promise,但终级方案仍是ES7议案中的async await
固然async await本质上也仍是Generator,能够算是Generator的语法糖。
因此这篇先来看下Generator.javascript

Generator语法

先来看个例子:java

function* getAsync(id: string){ yield 'id'; yield id; return 'finish'; } let p = getAsync('123'); console.info(p.next()); console.info(p.next()); console.info(p.next());

先看下和普通函数的区别,function后面多了一个*,变成了function*,函数体用到了yield,这个你们比较熟悉,C#也有,返回可枚举集合有时会用到。
在ES6里yield一样表示返回一个迭代器,因此用到的时候会用next()来顺序执行返回的迭代器函数。
上面代码返回的结果以下:git

{ value: 'id', done: false } { value: '123', done: false } { value: 'finish', done: true }

能够看到next()的结果是一个对象,value表示yield的结果,done表示是否真正执行完。
因此看到最后return了finishdone就变成true了,若是这时再继续执行next()获得的结果是{ value: undefined, done: true }.github

Generator原理和使用

Generator实际上是ES6对协程的一种实现,即在函数执行过程当中容许保存上下文同时暂停执行当前函数转而去执行其余代码,过段时间后达到条件时继续以上下文执行函数后面内容。
所谓协程其实能够看作是比线程更小的执行单位,一个线程能够有多个协程,协程也会有本身的调用栈,不过一个线程里同一时间只能有一个协程在执行。
并且线程是资源抢占式的,而协程则是合做式的,怎样执行是由协程本身决定。
因为JavaScript是单线程语言,自己就是一个不停循环的执行器,因此它的协程是比较简单的,线程和协程关系是 1:N。
一样是基于协程goroutine的go语言实现的是 M:N,要同时协调多个线程和协程,复杂得多。
Generator中碰到yield时会暂停执行后面代码,碰到有next()时再继续执行下面部分。typescript

当函数符合Generator语法时,直接执行时返回的不是一个确切的结果,而是一个函数迭代器,所以也能够用for...of来遍历,遍历时碰到结果done为true则中止。json

function* getAsync(id: string){ yield 'id'; yield id; return 'finish'; } let p = getAsync('123'); for(let id of p){ console.info(id); }

打印的结果是:promise

id 123

由于最后一个finishdone是true,因此for...of中止遍历,最后一个就不会打印出来。
另外,Generatornext()是能够带参数的,app

function* calc(num: number){ let count = yield 1 + num; return count + 1; } let p = calc(2); console.info(p.next().value); // 3 console.info(p.next().value); // NaN //console.info(p.next(3).value); // 4

上面的代码第一个输出是yield 1 + num的结果,yield 1返回1,加上传进来的2,结果是3.
继续输出第二个,按正常想法,应该输出3,可是因为yield 1是上一轮计算的,这轮碰到上一轮的yield时返回的老是undefined
这就致使yield 1返回undefined,undefined + num返回的是NaN,count + 1也仍是NaN,因此输出是NaN
注释掉第二个,使用第三个就能够返回预期的值,第三个把上一次的结果3用next(3)传进去,因此能够获得正确结果。
若是想一次调用全部,能够用此次方式来递归调用:异步

let curr = p.next(); while(!curr.done){ console.info(curr.value); curr = p.next(curr.value); } console.info(curr.value); // 最终结果

Generator能够配合Promise来更直观的完成异步操做。async

function delay(): Promise<void>{ return new Promise<void>((resolve, reject)=>{setTimeout(()=>resolve(), 2000)}); } function* run(){ console.info('start'); yield delay(); console.info('finish'); } let generator = run(); generator.next().value.then(()=>generator.next());

run这个函数来看,从上到下执行是很好理解的,先输出'start',等待2秒,再输出'finish'。
只是执行时须要不停的使用then,好在TJ大神写了CO模块,能够方便的执行这种函数,把Generator函数传给co便可。

co(run).then(()=>console.info('success'));

co的实现原理能够看下它的核心代码:

function co(gen) { var ctx = this; var args = slice.call(arguments, 1); return new Promise(function(resolve, reject) { if (typeof gen === 'function') gen = gen.apply(ctx, args); if (!gen || typeof gen.next !== 'function') return resolve(gen); onFulfilled(); //最主要就是这个函数,递归执行next()和then() function onFulfilled(res) { var ret; try { ret = gen.next(res); // next(), res是上一轮的结果 } catch (e) { return reject(e); } next(ret); // 里面调用then,并再次调用onFulfilled()实现递归 return null; } function onRejected(err) { // 处理失败的状况 var ret; try { ret = gen.throw(err); } catch (e) { return reject(e); } next(ret); } function next(ret) { if (ret.done) return resolve(ret.value); // done是true的话表示完成,结束递归 var value = toPromise.call(ctx, ret.value); if (value && isPromise(value)) return value.then(onFulfilled, onRejected); //递归onFulfilled return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, ' + 'but the following object was passed: "' + String(ret.value) + '"')); } }); }

能够看到co的核心代码和我上面写的递归调用Generator函数的本质是同样的,不断调用下一个Promise,直到done为true。

纵使有co这个库,可是使用起来仍是略有不爽,下篇就轮到async await出场,前面这两篇都是为了更好的理解下一篇。

相关文章
相关标签/搜索