总目录

从C#到TypeScript - Generator

上篇讲了PromisePromise的执行需要不停的调用then,虽然比callback要好些,但也显得累赘。所以ES6里添加了Generator来做流程控制,可以更直观的执行Promise,但终级方案还是ES7议案中的async await

当然async await本质上也还是Generator,可以算是Generator的语法糖。

所以这篇先来看下Generator.

Generator语法

先来看个例子:

  1. function* getAsync(id: string){
  2. yield 'id';
  3. yield id;
  4. return 'finish';
  5. }
  6. let p = getAsync('123');
  7. console.info(p.next());
  8. console.info(p.next());
  9. console.info(p.next());

先看下和普通函数的区别,function后面多了一个*,变成了function*,函数体用到了yield,这个大家比较熟悉,C#也有,返回可枚举集合有时会用到。

在ES6里yield同样表示返回一个迭代器,所以用到的时候会用next()来顺序执行返回的迭代器函数。

上面代码返回的结果如下:

  1. { value: 'id', done: false }
  2. { value: '123', done: false }
  3. { value: 'finish', done: true }

可以看到next()的结果是一个对象,value表示yield的结果,done表示是否真正执行完。

所以看到最后return了finishdone就变成true了,如果这时再继续执行next()得到的结果是{ value: undefined, done: true }.

Generator原理和使用

Generator其实是ES6对协程的一种实现,即在函数执行过程中允许保存上下文同时暂停执行当前函数转而去执行其他代码,过段时间后达到条件时继续以上下文执行函数后面内容。

所谓协程其实可以看做是比线程更小的执行单位,一个线程可以有多个协程,协程也会有自己的调用栈,不过一个线程里同一时间只能有一个协程在执行。

而且线程是资源抢占式的,而协程则是合作式的,怎样执行是由协程自己决定。

由于JavaScript是单线程语言,本身就是一个不停循环的执行器,所以它的协程是比较简单的,线程和协程关系是 1:N。

同样是基于协程goroutine的go语言实现的是 M:N,要同时协调多个线程和协程,复杂得多。

Generator中碰到yield时会暂停执行后面代码,碰到有next()时再继续执行下面部分。

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

  1. function* getAsync(id: string){
  2. yield 'id';
  3. yield id;
  4. return 'finish';
  5. }
  6. let p = getAsync('123');
  7. for(let id of p){
  8. console.info(id);
  9. }

打印的结果是:

  1. id
  2. 123

因为最后一个finishdone是true,所以for...of停止遍历,最后一个就不会打印出来。

另外,Generatornext()是可以带参数的,

  1. function* calc(num: number){
  2. let count = yield 1 + num;
  3. return count + 1;
  4. }
  5. let p = calc(2);
  6. console.info(p.next().value); // 3
  7. console.info(p.next().value); // NaN
  8. //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)传进去,所以可以得到正确结果。

如果想一次调用所有,可以用这次方式来递归调用:

  1. let curr = p.next();
  2. while(!curr.done){
  3. console.info(curr.value);
  4. curr = p.next(curr.value);
  5. }
  6. console.info(curr.value); // 最终结果

Generator可以配合Promise来更直观的完成异步操作。

  1. function delay(): Promise<void>{
  2. return new Promise<void>((resolve, reject)=>{setTimeout(()=>resolve(), 2000)});
  3. }
  4. function* run(){
  5. console.info('start');
  6. yield delay();
  7. console.info('finish');
  8. }
  9. let generator = run();
  10. generator.next().value.then(()=>generator.next());

run这个函数来看,从上到下执行是很好理解的,先输出'start',等待2秒,再输出'finish'。

只是执行时需要不停的使用then,好在TJ大神写了CO模块,可以方便的执行这种函数,把Generator函数传给co即可。

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

co的实现原理可以看下它的核心代码:

  1. function co(gen) {
  2. var ctx = this;
  3. var args = slice.call(arguments, 1);
  4. return new Promise(function(resolve, reject) {
  5. if (typeof gen === 'function') gen = gen.apply(ctx, args);
  6. if (!gen || typeof gen.next !== 'function') return resolve(gen);
  7. onFulfilled(); //最主要就是这个函数,递归执行next()和then()
  8. function onFulfilled(res) {
  9. var ret;
  10. try {
  11. ret = gen.next(res); // next(), res是上一轮的结果
  12. } catch (e) {
  13. return reject(e);
  14. }
  15. next(ret); // 里面调用then,并再次调用onFulfilled()实现递归
  16. return null;
  17. }
  18. function onRejected(err) { // 处理失败的情况
  19. var ret;
  20. try {
  21. ret = gen.throw(err);
  22. } catch (e) {
  23. return reject(e);
  24. }
  25. next(ret);
  26. }
  27. function next(ret) {
  28. if (ret.done) return resolve(ret.value); // done是true的话表示完成,结束递归
  29. var value = toPromise.call(ctx, ret.value);
  30. if (value && isPromise(value)) return value.then(onFulfilled, onRejected); //递归onFulfilled
  31. return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
  32. + 'but the following object was passed: "' + String(ret.value) + '"'));
  33. }
  34. });
  35. }

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

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

从C#到TypeScript - Generator的更多相关文章

  1. C# vs TypeScript - 高级类型

    总目录 从C#到TypeScript - 类型 从C#到TypeScript - 高级类型 从C#到TypeScript - 变量 从C#到TypeScript - 接口 从C#到TypeScript ...

  2. 从C#到TypeScript - 变量

    总目录 从C#到TypeScript - 类型 从C#到TypeScript - 高级类型 从C#到TypeScript - 变量 从C#到TypeScript - 接口 从C#到TypeScript ...

  3. 从C#到TypeScript - 接口

    总目录 从C#到TypeScript - 类型 从C#到TypeScript - 高级类型 从C#到TypeScript - 变量 从C#到TypeScript - 接口 从C#到TypeScript ...

  4. 从C#到TypeScript - 类

    总目录 从C#到TypeScript - 类型 从C#到TypeScript - 高级类型 从C#到TypeScript - 变量 从C#到TypeScript - 接口 从C#到TypeScript ...

  5. 从C#到TypeScript - function

    总目录 从C#到TypeScript - 类型 从C#到TypeScript - 高级类型 从C#到TypeScript - 变量 从C#到TypeScript - 接口 从C#到TypeScript ...

  6. 从C#到TypeScript - 装饰器

    总目录 从C#到TypeScript - 类型 从C#到TypeScript - 高级类型 从C#到TypeScript - 变量 从C#到TypeScript - 接口 从C#到TypeScript ...

  7. 从C#到TypeScript - Promise

    总目录 从C#到TypeScript - 类型 从C#到TypeScript - 高级类型 从C#到TypeScript - 变量 从C#到TypeScript - 接口 从C#到TypeScript ...

  8. 从C#到TypeScript - async await

    总目录 从C#到TypeScript - 类型 从C#到TypeScript - 高级类型 从C#到TypeScript - 变量 从C#到TypeScript - 接口 从C#到TypeScript ...

  9. 从C#到TypeScript - Reflect

    总目录 从C#到TypeScript - 类型 从C#到TypeScript - 高级类型 从C#到TypeScript - 变量 从C#到TypeScript - 接口 从C#到TypeScript ...

随机推荐

  1. [Angular Tutorial] 14 -Animations

    在这一步中,我们将会通过在我们先前创建的模板代码中添加CSS和JavaScript动画效果来扩展我们的web应用. ·我们现在使用ngAnimate模块来允许动画效果贯穿整个应用. ·我们也依赖于自带 ...

  2. C#索引器的用法

    索引器允许类或者结构的实例按照与数组相同的方式进行索引取值,索引器与属性类似,不同的是索引器的访问是带参的. 索引器和数组比较: (1)索引器的索引值(Index)类型不受限制 (2)索引器允许重载 ...

  3. 弹出式菜单(下拉菜单)实现——PopupMenu

    PopupMenu代表弹出式菜单,它会在指定组件上弹出PopupMenu,默认情况下,PopupMenu会显示在该组件的下方或上方.PopupMenu可增加多个菜单项,并可为菜单项增加子菜单. 使用P ...

  4. bug工具

    在线工具:柠檬bug管理--兼顾项目管理 开源工具:PPM Bug 缺陷管理系统 项目管理.bug管理:http://www.bugfree.cn

  5. nginx 特定目录禁止php执行

    LNMP有一个缺点就是目录权限设置上不如Apache,有时候网站程序存在上传漏洞或类似pathinfo的漏洞从而导致被上传了php木马,而给网站和服务器带来比较大危险. 建议将网站目录的PHP权限去掉 ...

  6. Java数据流的一般操作规律总结

    流的操作规律: 1,明确源和目的. 数据源:就是需要读取,可以使用两个体系:InputStream.Reader: 数据汇:就是需要写入,可以使用两个体系:OutputStream.Writer: 2 ...

  7. Delphi 常用函数记录

    //判断是否是数字 function IsNumeric(sDestStr: string): Boolean; //简写多余汉字 function SimplifyWord(sWord: strin ...

  8. #搜索# #BFS# #优先队列# ----- OpenJudge鸣人和佐助

    OpenJudge 6044:鸣人和佐助 总时间限制: 1000ms  内存限制: 65536kB 描述 佐助被大蛇丸诱骗走了,鸣人在多少时间内能追上他呢? 已知一张地图(以二维矩阵的形式表示)以及佐 ...

  9. ubuntu vi编辑insert时上下左右建为ABCD

    ubuntu  在vi编辑insert时上下左右建不能移动光标而是输出ABCD,backspace也不能起删除作用, 开始我退出insert模式就能够移动和删除了,不过这样太麻烦很不适应, 只要一次执 ...

  10. 使用ActionBar实现下拉式导航

    ActionBar除可提供Tab导航支持之外,还提供了下拉式(DropDown)导航方式.下拉式导航的ActionBar在顶端生成下拉列表框,当用户单击某个列表项时,系统根据用户单击事件导航指定Fra ...