为什么要用Generator函数处理异步

最近看了阮一峰老师ES6 Generator 函数的异步应用,一开始没理解为什么要用Generator去解决异步嵌套的问题。网上搜索结果也都比较生硬,不太好理解。所以写了下面这篇文章,帮自己理一下思路。因为是对阮老师文章的解读,所以文章中大量引用阮老师文章和代码,在此一起谢过阮老师。

首先需要你了解Generator函数。

我们先看下阮老师的文章,协程​​​​​​​,协程的-Generator-函数实现。这里应该主要是考虑嵌套异步的情况。如果只有一个异步,callback或者promise还是很清晰的。多个回调函数嵌套的时候callback和promise会有这些缺点
Ps:自己觉得callback 回调参数抽出独立的函数就没有上述问题了,还是很清晰,很好理解的。

嵌套异步就要控制上一次异步执行完成之后,再执行下一个异步。Generator函数可以控制什么时候执行next,如果yield 后面的表达式是异步代码,那就可以控制每一个异步执行。

假如我们能拿到每一步的结果,并将它传递给下一个yield。循环这个过程,就能实现嵌套的异步。需要解决的两个问题,第一拿到yield后面表达式的异步结果,第二异步结果传递给下一个yield表达式。

第一个问题 拿到yield后面表达式的异步结果
next函数返回值是{value:xx,done:boolean}结构,
其中value属性是yield后面表达式的结果。例如下面代码,hw.next()返回结果的value就是'hello'

function* helloWorldGenerator() {
    yield 'hello';//'hello'表达式的结果就是'hello'。如果写成这样就好理解点 yield return 'hello';当然这样是有语法错误的。
    return 'ending';
  }
  
  var hw = helloWorldGenerator();

  hw.next()
// { value: 'hello', done: false }

如果表达式是一个异步操作,返回值肯定不是异步结果,因为这里yield直接交出执行权了,不会等异步返回结果。那么只能通过表达式的返回值拿到异步结果。那么这个返回值肯定不是基本类型,Number、Boolean、String、NULL、Undefined,Symbol这些。引用类型Array,Date都是存储数据的,Object范围又太大,一切皆Object。Function比较合适,如果yield后面的表达式返回的是一个函数,如果我们调用这个函数能得到异步结果就可以了。


第二个问题 异步结果传递给下一个yield 表达式
可以通过next函数的参数实现。注意next参数并不是传递给yield后面的表达式的,而是传递给yield左边的变量的。


具体怎么让一个调用异步方法的表达式返回一个函数呢?答案就是用Thunk函数,简单的来说Thunk函数就是将一个有回调参数的函数变形。
将fun(para1,para2,…,callback)变形成funThunk(para1,para2,…)(callback)。funThunk函数返回值是另一个函数,可以接受回调参数。貌似很好的解决了问题,上代码。

var fs = require('fs'); //加载fs模块,一个异步读取文件的模块。
var thunkify = require('thunkify');//加载thunkify(Thunk的封装)模块
var readFileThunk = thunkify(fs.readFile);//将fs.readFile函数包装成名为readFileThunk的Thunk函数

var gen = function* (){
  var r1 = yield readFileThunk('/etc/fstab');//调用readFileThunk,会返回一个函数
  console.log(r1.toString());
  var r2 = yield readFileThunk('/etc/shells');
  console.log(r2.toString());
};
var g = gen();
var r1 = g.next();//执行 readFileThunk('/etc/fstab'),并返回结果,r1.value就是readFileThunk返回的函数

r1.value(function (err, data) {//调用readFileThunk返回的函数,将回调函数传入。
  if (err) throw err;
  var r2 = g.next(data);//回调函数中继续调用next,并将异步结果传入。
  r2.value(function (err, data) {
    if (err) throw err;
    g.next(data);
  });
});

上面代码中可以看到“将同一个回调函数,反复传入next方法的value属性”。也就引出了 Thunk 函数的自动流程管理 

function run(fn) {//run函数的参数是一个Generator函数
    var gen = fn();//得到Generator函数指针
  
    function next(err, data) {//定义next方法
      var result = gen.next(data);//调用next,并传入data,result.value为Thunk函数返回的函数。
      if (result.done) return;//如果Generator结束了就返回
      result.value(next);//否则调用result.value,next函数作为回调函数。这样当得到异步结果之后,就会调用next()
    }
  
    next();//执行next方法,第一次err,data都是undefined。
  }
  
  function* g() {//Generator函数,要求yield 后面都是Thunk函数。
    // ...
  }
  
  run(g);//执行run函数,传入Generator函数。

只要定义好Generator函数,编排好异步操作。将Generator函数当做参数传入run,异步操作就可以一个接着一个的执行了。

可能有人问了,这种方式是异步操作串行执行,怎么编排有并行情况的异步呢,答案就是再一次yield中用promise实现并行(ps:自己并没有实验)。

是不是还是不太好理解,自己拿纸画一下就好理解了。我觉得最不好理解的就是Thunk函数,他的返回值还是一个函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值