前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ES6中的Generator函数

ES6中的Generator函数

作者头像
张子阳
发布2018-09-30 09:52:59
4460
发布2018-09-30 09:52:59
举报

ES6中的Generator函数

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

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

创建Generator函数

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

代码语言:txt
复制
function* generator(){
    console.log("a")
}

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

代码语言:txt
复制
let iterator = generator();     // 并不会输出a

运行Generator函数

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

代码语言:txt
复制
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关键字,从而实现函数的分段执行。

代码语言:txt
复制
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()方法。

代码语言:txt
复制
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 遍历迭代器

代码语言:txt
复制
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);
}

上面代码的输出是:

代码语言:txt
复制
"1"
"A"
"2"
"B"
"3"

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

串联多个Generator函数

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

代码语言:txt
复制
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()函数改写成了异步执行的方式,如下例所示:

代码语言:txt
复制
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的同学来说,算是一个前导的知识点。

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-3-6,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ES6中的Generator函数
    • 创建Generator函数
      • 运行Generator函数
        • 使用yield关键字
          • 向Generator函数进行传值
            • 使用for...of 遍历迭代器
              • 串联多个Generator函数
                • 使用Generator修改回调代码
                  • 总结
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档