ES6中的Generator函数

ES6中的Generator函数

2018-3-6 作者: 张子阳 分类: Web前端

之前在React项目中,遇到异步请求,都是通过redux-thunk来处理,但使用这种方式,action就变得不那么纯净了。当前新的趋势是使用redux-saga来处理side effects(副效应)。在redux-saga中,重度使用了generator函数的概念,这篇文章先就Generator函数做一个小结。

创建Generator函数

与普通函数的声明不同,Generator函数需要在function关键字后面加星号*。

function* generator(){
    console.log("a")
}

执行Generator函数并不会运行函数体,而是返回一个迭代器iterator对象。

let iterator = generator();     // 并不会输出a

运行Generator函数

如果想要运行generator函数,则需要在迭代器上执行next()方法。

function* generator(){
    console.log("a")
    return "a";
}

let iterator = generator();
let state = iterator.next();        // 输出a
console.log(state);                 // 输出 [object Object] {  done: true, value: "a" }

next()方法返回了一个对象,该对象有两个参数,done参数表明了generator函数是否执行完毕,value则为函数的返回值。

使用yield关键字

目前看上去Generator函数好像并没有什么用,实际上,它可以结合yield关键字,从而实现函数的分段执行。

function* generator(){
    console.log("a1")
    yield "a2";
  
    console.log("b1")
    yield "b2";
  
    console.log("c1")
    return "c2";
}

let iterator = generator();
let state = iterator.next();        // 输出a1
console.log(state);                 // 输出 [object Object] { done: false,  value: "a2" }

state = iterator.next();        // 输出b1
console.log(state);             // 输出 [object Object] { done: false,  value: "b2" }

state = iterator.next();        // 输出b3
console.log(state);             // 输出 [object Object] { done: true,  value: "b3" }

state = iterator.next();        // 无输出
console.log(state);             // 输出 [object Object] { done: true, value: undefined }

执行上面的代码,可以看到,函数每运行到yield的位置就暂停了,直到下一次执行next()。

向Generator函数进行传值

从上面的例子,可以看到,通过使用yield和return,可以获取Generator函数每段执行的返回值。那么如何向函数中传入值?可以通过函数的参数和next()方法。

function* generator(p){
  
    console.log("1: " + p)
    p = yield "X";
  
    console.log("2: " + p)
    p = yield "Y";
  
    console.log("3: " + p)
}

let iterator = generator("A");

let state = iterator.next("B");     // 输出 1: A
console.log(state);                 // 输出 [object Object] { done: false,  value: "X" }

state = iterator.next("C");         // 输出 2: C
console.log(state);                 // 输出 [object Object] { done: false,  value: "Y" }

state = iterator.next("D");         // 输出 3: D
console.log(state);                 // 输出 [object Object] { done: true, value: undefined }

这里的规则是:第x调用next()方法时传入的参数,是第x-1次调用yield的返回值。当x=1,也就是第1次调用next()方法时,因为此时还从来没有调用过yield,因此输入参数会被丢弃(如上栗例中没有输出B)。此时,如果要传入参数,则应使用generator函数的输入参数。

使用for...of 遍历迭代器

function* generator() {
    console.log("1");
    yield "A";
  
    console.log("2");
    yield "B";    
  
    console.log("3");
    return "C";
};

let iterator = generator();

for (let value of iterator) {
    console.log(value);
}

上面代码的输出是:

"1"
"A"
"2"
"B"
"3"

注意到并没有输出C,因为这个遍历只对yield有效。既然已经可以利用yiled获得函数任意执行阶段的返回值,所以建议generator函数中不要再使用return,这样可以统一访问方式。将原先需要return的返回值,放到最后一个yield即可。

串联多个Generator函数

可以通过yield* 串联Generator函数。

function* gen1() {
    yield "A";
    yield* gen2();
};

function* gen2(){
    yield "B";
    yield* gen3();
}

function* gen3(){
    yield "C";    
}


let iterator = gen1();

for (let value of iterator) {
    console.log(value);         // 输出: A B C
}

使用Generator修改回调代码

和Promise一样,Generator函数也可以将异步请求中的层层回调,改写成串行化的方式。将add()函数改写成了异步执行的方式,如下例所示:

const add = (x, y, advancer) => {
    setTimeout(() => {
        let sum = x+y;     
        advancer(sum);
    }, 200);
};

const curry = (method, ...args) => {
    var value = (advancer) => {
        args.push(advancer);   
        return method.apply({}, args);
    }
    
    return value;
};
   
const controller = (generator) => {
    const iterator = generator();
   
    const advancer = (response) => {
        var state;   
        state = iterator.next(response);
   
        if (!state.done) {            
            state.value(advancer);
        }
    }   
    advancer();
};

function* generator(){
    var value = curry(add, 1, 2); 
    
    const sum1 = yield value;
    const sum2 = yield curry(add, sum1, 3);
    const sum3 = yield curry(add, sum2, 4);
   
    console.log(sum1, sum2, sum3);
}

controller(generator);

上面代码的核心在于,add方法的回调函数并非直接写死,而是改成参数,由advancer递归传入,这样使得只要state.done为false,就会依次串行执行。

总结

这篇文章简要地介绍了Generator函数及其作用,对于想要熟悉Redux/Saga的同学来说,算是一个前导的知识点。

感谢阅读,希望这篇文章能给你带来帮助!

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏阮一峰的网络日志

co 函数库的含义和用法

======================================== 以下是《深入掌握 ECMAScript 6 异步编程》系列文章的第三篇。 ...

3345
来自专栏LIN_ZONE

java反射机制的简单使用

通过上面的代码可以获得 运行时类的对象,然后下面使用运行时类的对象来构造一个反射工具类,通过下面这个类 可以利用反射机制实例化该类的对象,设置对象的属性并调用对...

832
来自专栏Golang语言社区

【Go 语言社区】Go学习笔记:json处理

Encode 将一个对象编码成JSON数据,接受一个interface{}对象,返回[]byte和error: func Marshal(v interfac...

64612
来自专栏编程

使用dict和set

Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。-...

22110
来自专栏全沾开发(huā)

JavaScript异步编程:Generator与Async

1624
来自专栏Zephery

2017-03-14学习笔记

1.Integer和int,装箱拆箱 1、基本型和基本型封装型进行“==”运算符的比较,基本型封装型将会自动拆箱变为基本型后再进行比较,因此Integer(0)...

35414
来自专栏函数式编程语言及工具

FunDA(8)- Static Source:保证资源使用安全 - Resource Safety

   我们在前面用了许多章节来讨论如何把数据从后台数据库中搬到内存,然后进行逐行操作运算。我们选定的解决方案是把后台数据转换成内存中的数据流。无论在打开数据库表...

21410
来自专栏Ldpe2G的个人博客

Scala typeclass 设计模式

本文的写作的灵感主要是看了这个视频 : Tutorial: Typeclasses in Scala with Dan Rosen

1556
来自专栏AndroidTv

学点Groovy来理解build.gradle代码

在写这篇博客时,搜索参考了很多资料,网上对于 Groovy 介绍的博客已经特别多了,所以也就没准备再详细的去介绍 Groovy,本来也就计划写一些自己认为较重要...

3948
来自专栏xingoo, 一个梦想做发明家的程序员

Oozie分布式工作流——EL表达式

oozie支持使用EL(expression language)表达式。 基本的EL常量 KB MB GB TB PB 基本EL函数 string fir...

2568

扫码关注云+社区

领取腾讯云代金券