es6之generator函数的应用

es6之generator函数的应用

最近在学习到redux-saga用法的时候,发现其中大量用到了es6中新增的Generator函数,在学习Generator函数的同时,整理了一些关于Generator函数的基本应用,这篇文章不涉及到Generator函数的基本用法,关于基本语法知识大家可以参考这里

Generator 与状态机

如果我们想实现一个功能,函数每次执行一次就改变一次状态,要怎么实现?如果你使用es5实现你可能会这样做:

//ES5
var ticking = true;
var clock = function() {
  if (ticking)
    console.log('Tick!');
  else
    console.log('Tock!');
  ticking = !ticking;
}

这么写可以实现,但是会存在一个问题,我们需要定义一个全局变量来保存内部状态,有没有更好的实现方式呢?当然!!下面看下es6的实现方式:

//ES6
const clock = function* () {
  while (true) {
      console.log('Tick!');
      yield;
      console.log('Tock!');
      yield;
  }
};

const gen = clock();
gen.next(); // Tick!
gen.next(); // Tock!
gen.next(); // Tick!
gen.next(); // Tock!

这里定义了一个Generator函数clock,当执行第一个next()方法时,打印出'Tick!',这个时候遇到yield关键字,函数暂时停止执行并跳出;执行第二个next()方法,函数接着第一个yield语句后面继续执行,打印出'Tock!',遇到yield关键字,函数停止执行并跳出;这个时候Generator函数里面的两个yield语句就执行完毕了,那么我们如何实现有周期的改变状态呢?实现的方法就是借助于Generator函数体里面的while(){...}语句。当我们继续执行第三个next()语句,此时函数会重复执行while循环里面的第一个console方法,打印出'Tick!',接着打印出'Tock!',如此循环往复。

Generator 异步操作的同步化表达

下面要介绍的部分是Generator函数的应用中应用比较广泛的。Generator函数可以利用yield关键字暂停函数执行,利用这一特性,我们可以将耗时的异步操作放在yield关键字之后,这样等到调用next()方法的时候再继续往后执行,这样实际上就省略了回调函数。看下面的例子:

function apiInterface(url) {
  ajaxCall(url, function(response){
    it.next(response);
  });
}

function* getUserInfo() {
  var result = yield apiInterface("http://test/getUserList");
  var resp = JSON.parse(result);
    console.log(resp.value);
}

var it = getUserInfo();
it.next();

这里定义了一个Generator函数getUserInfoyield关键字后面跟着一个调用ajax方法获取信息的方法,调用it.next()开始执行apiInerface方法,apiInerface方法里面又执行了一次next(),并同时将ajax请求的返回值response作为参数传入next(),注意,这个response参数一定要传,否则result值为undefined,这里要记住一句话:next方法参数的作用,是覆盖掉上一个yield语句的值

再看一个例子:

function* loadUI() {
  showLoadingScreen();
  yield loadUIDataAsynchronously();
  hideLoadingScreen();
}
var loader = loadUI();
// 加载UI
loader.next()

// 卸载UI
loader.next()

上面代码中,第一次调用loadUI函数时,该函数不会执行,仅返回一个遍历器。下一次对该遍历器调用next方法,则会显示Loading界面,并且异步加载数据。等到数据加载完成,再一次使用next方法,则会隐藏Loading界面。可以看到,这种写法的好处是所有Loading界面的逻辑,都被封装在一个函数,按部就班非常清晰。

在看一个复杂点的例子:

如果现在有三个比较耗时的操作step1step2step3,现在想先执行step1,然后将执行step1之后得到的值value1作为参数传入step2,再将执行step2之后得到的结果value2作为参数执行step3,如果使用回调的方式可以这样编写代码:

step1(function (value1) {
  step2(value1, function(value2) {
    step3(value2, function(value3) {
    // do something with value3
    });
  });
});

如果将上面的需求用Promise方法改写可以这么写:

step1().then(function(value1) {
	return step2(value1);
}).then(function(value2){
    return step3(value2);
}) .then(function(value3) {
    // do something with value3
});

Promise的写法可以很好的避免出现回调函数的情况,注意,这里我们假设step1step2step3是三个Promise对象。

最后我们看下使用Generator函数如何改造上述需求:

function* someTask(value1) {
  try {
    var value2 = yield step1(value1);
    var value3 = yield step2(value2);
    var value4 = yield step3(value3);
    // Do something with value4
  } catch (e) {
    // Handle any error from step1 through step4
  }
}

然后定义一个调度函数一次执行三个任务:

scheduler(someTask(initialValue));

function scheduler(task) {
  var taskObj = task.next(task.value);
  // 如果Generator函数未结束,就继续调用
  if (!taskObj.done) {
    task.value = taskObj.value
    scheduler(task);
  }
}

通过Generator函数将回调方式改成了线性方式,但是上述方式只适合同步操作,因为这里的代码在得到上一步的结果之后就会立即执行,没有判断异步操作合适结束。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值