从C#到TypeScript - Generator

从C#到TypeScript - Generator

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

Generator语法

先来看个例子:

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()来顺序执行返回的迭代器函数。 上面代码返回的结果如下:

{ value: 'id', done: false }
{ value: '123', done: false }
{ 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则停止。

function* getAsync(id: string){
    yield 'id';
    yield id;
    return 'finish';
}

let p = getAsync('123');
for(let id of p){
    console.info(id);
}

打印的结果是:

id
123

因为最后一个finishdone是true,所以for...of停止遍历,最后一个就不会打印出来。 另外,Generatornext()是可以带参数的,

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来更直观的完成异步操作。

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出场,前面这两篇都是为了更好的理解下一篇。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏专知

关关的刷题日记13——Leetcode 414. Third Maximum Number

关小刷刷题13 – Leetcode 414. Third Maximum Number 题目 Given a non-empty array of integ...

3159
来自专栏Java3y

包装模式就是这么简单啦

1484
来自专栏java架构师

金三银四跳槽季,BAT美团滴滴java面试大纲(带答案版)之一:Java基础篇

Java基础篇: 题记:本系列文章,会尽量模拟面试现场对话情景, 用口语而非书面语 ,采用问答形式来展现。另外每一个问题都附上“延伸”,这部分内容是帮助小伙伴们...

2529
来自专栏海说

深入理解计算机系统(3.2)---数据格式、访问信息以及操作数指示符

  本文的内容其实可以成为汇编语言的基础,因为汇编语言大部分时候是在操作一些我们平时开发看不到的东西,因此本文的目的就是搞清楚,汇编语言都是在操作些什么东西。或...

764
来自专栏写代码的海盗

我们是80后 golang入坑系列

现在这个系列,已经开始两极分化了。 点赞的认为风格轻松,看着不困。反之,就有人嫌写的罗里吧嗦,上纲上线。所以善意提醒,里面不只是技术语言,还有段子。专心看技术的...

3307
来自专栏ImportSource

来自JVM的一封ClassFile介绍信

我是一个class文件。我的内部是由一个被叫做ClassFile的structure组成。 我在jvm中占有很重要的地位,你可去看看jvm规范中我占了多少篇幅...

3588
来自专栏数说工作室

1. PRXMATCH () | 提取文本数据,分析师小王初上手!

【SAS Says·扩展篇】分析师小王初上手! | 1. PRXMATCH () 本集目录: 0. 小王初上手 1. 初始PRXMATCH() 2. metac...

2626
来自专栏写代码的海盗

入坑第二式 golang入坑系列

史前必读: 这是入坑系列的第二式,如果错过了第一式,可以去gitbook( https://andy-zhangtao.gitbooks.io/golang/c...

2394
来自专栏JAVA高级架构开发

Java 程序员必须掌握的 8 道数据结构面试题,你会几道?

瑞士计算机科学家Niklaus Wirth在1976年写了一本书,名为《算法+数据结构=编程》。

1240
来自专栏菜鸟前端工程师

JavaScript学习笔记004-if判断0for循环

783

扫码关注云+社区