Generator函数与异步编程

Posted Web手艺人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Generator函数与异步编程相关的知识,希望对你有一定的参考价值。

一、Generator函数

1、简介

ECMScript2015标准推出已有3年时间了,各类教程中对Generator函数的讨论已经很多,本篇文章只做简要介绍,仅对Generator在异步编程中的表现做讨论,如果对Generator还不熟悉的话,可要抓紧时间补补功课了哟~

       Generator函数的出现使我们第一次打破了js语言运行时的函数完整性,使函数可以交出执行权,从而达到暂停执行的目的。那么Generator函数会在哪里暂停执行呢。

2yield语句(表达式)

    执行Generator函数会返回一个迭代器,第一次调用迭代器的next()的方法来启动函数并在第二个yield语句处暂停。再次调用next()方法可以从第二个yield处开始处继续执行函数,在第三个yield处暂停,依次类推直到整个函数执行完毕。考虑如下代码:


function *foo(x) {    
   var y = x * (yield) }
var it = foo(6); it.next();
var res = it.next(7); res.value; // 42


        如果没有读过Generator相关概念,这段代码看起来可能有点怪,为什么x会乘一个语句,(yield是有返回值的,所以有的教程里会叫表达式,到底叫什么,我们这里不纠结,毕竟这不是重点)这就是Generator最牛逼的特点了,函数在执行过程中依然可以进行输入和输出。

3、双向通信

        刚才我们说到x * 后面的yield语句有一个返回值,聪明的你可能已经猜到了那句it.next(7)就是这个yiled语句返回值(6*7=42 ^_^),这个7就是由it.next()函数传入的。继续考虑如下代码:


function *foo(){
  var y = x * (yield ‘helloworld’);
  return y;
}
var it = foo(6);  
var res = it.next(); //不带参数
res.value; //helloworld
res = it.next(7); res.value;//42


        我们看到第一个it.next()没有带任何参数,它的作用是启动foo函数,同时在这提出一个问题,foo函数要把什么值传给我?那么谁来回答这个问题呢?就是第一个yield语句了,所以我们可以看到,第一次输出res.value的值是helloworld。yield在向函数外抛出结果的同时也提出了一个问题:我要把什么值放在这个表达式里?回答这个问题的就是下一个it.next(7),如你所愿,变量y的值为42。

        这里似乎没有第二个yield来回答第二个yield的问题了,别担心,最后的

return语句完成了这个任务。

二、使用Generator函数异步编程

        有了前面的基础,来理解Generator在异步编程中的作用就容易的多了,上一节我们提到,可以利用yield语句和next方法进行双向通信,可以利用yield语句暂停Generator函数的执行,直到下一个next方法执行时恢复。基于以上两点我们很容易的想到,类似ajax请求这样的异步编程是不是可以利用yield阻塞函数,直到某个地方调用了next()将请求传进来。形如下面的写法


function *foo(url) {
   var res = yield ajax(url); }



        答案是肯定的,考虑如下代码

function getData(url) {    ajax(url, function(data) {
       if(data.errno!==0){          it.throw(errmsg)          } else {          it.next(data)          }    }); }
function *main(){
 try {      var text = yield getData('api/data'
);  } catch (err) {
     console.log(err)  
 } } var it = main(); it.next();
//启动!

// 这里暂时忽略
try {

 it.next(); } catch (err) {
 
console.log(err) }

        首先定义了一个getData函数来异步获取数据,再定义一个main函数作为我们的主函数。通过第一个it.next()启动后,函数在yield处暂停,在getData方法的ajax回调中再次执行it.next将请求结果传入并恢复执行。

        你可能已经注意到我在上述代码中加入了try、catch,这就是Generator另一个强大之处,传统的ajax回调是无法直接进行错误传递的,或者说想实现同样的功能需要维护一套颇具复杂性的代码。迭代器提供了throw方法可以将外部的异常直接传入内部。那么既然消息可以双向传递,异常也可以双向传递,回头看上面暂时忽略部分的代码,当Generator函数内部发生异常时,对外部迭代器try catch也是可以拿到的。就是一个,叼。

三、Generator与Promise结合

       Generator与Promise结合可以说是ES6阶段最强大的异步编程方法,考虑如下代码:


function getData(url){
   return fetch(url);// fetch返回一个promise
}
function *main(){
   try {
       var res = yield getData('api/data')    } catch(err) {
       console.log(err)    } }
var it = main();
var p = it.next().value; p.then(res => {  it.next(res) },rej => {  it.throw(err) });


        乍一看好像没什么牛逼的地方,不就是callback变成了promise么。如果Generator函数可以自动执行,不需要我们手动next,那才牛逼了。

    Generator函数的自动执行器开源的方案有很多,其中最著名的是由TJ发布的co,node框架koa更是将中间件强制规定为Generator函数并自动执行。co 模块的思路就是利用 generator 的这个特性,将异步操作跟在 yield 后面,当异步操作完成并返回结果后,再触发下一次 next() 。当然,跟在 yield 后面的异步操作需要遵循一定的规范 thunks 和 promises。利用co我们可以写出如下的代码来自动执行Generato函数


co(
   function * () {
    var file1 = yield readFile('public.pem');
    console.log(file1.toString("base64"));
});

    co的源码在这就不介绍了,大家可以上github上自行学习。


以上是关于Generator函数与异步编程的主要内容,如果未能解决你的问题,请参考以下文章

深入掌握 ECMAScript 6 异步编程:Generator 函数的含义与用法

使用Generator函数进行异步编程

js generator

ES6之Generator

Generator 函数的语法

Generator 函数的语法