资讯专栏INFORMATION COLUMN

ES6 异步编程之一:Generator

Eidesen / 2945人阅读

摘要:生成器是原生提供的异步编程方案,其语法行为和传统函数完全不同,阮大的入门一书中对生成器有比较详尽的介绍,还有一些其他的文章可以参考,比如入门深入浅出三生成器深入浅出十一生成器,续篇本文主要是通过一些代码示例来记录和总结生成器的用法。

Generator

生成器是es6原生提供的异步编程方案,其语法行为和传统函数完全不同,阮大的《ECMAScript 6 入门》一书中对生成器有比较详尽的介绍,还有一些其他的文章可以参考,比如:

《ECMAScript 6 入门:generator》

深入浅出ES6(三):生成器 Generators

深入浅出ES6(十一):生成器 Generators,续篇

本文主要是通过一些代码示例来记录和总结生成器的用法。

yield 和 next

yieldnext在生成器中扮演着非常重要的角色,前者是一个操作符,后者是生成器上的一个函数。

他们具有以下特性:

需要调用generator的next函数,生成器中的语句才开始执行;

next函数在生成器之外调用,意味着可以在生成器之外控制其内部操作的执行过程;

当生成器执行到yield操作符就立即执行yield之后的语句并暂停,不敢妄言内部原理,姑且感性地比作savepoint;

当再次调用生成器的next函数时,生成器从上次发生yield"savepoint"继续执行,直到再次遇到yield,或者遇到是return或者throw生成器就退出;

next的返回值是一个形如{done:false, value:x}的对象,每次调用next都会使生成器继续执行,对于next的返回值有如下规律:

如果再次遇到yieldnext返回值中的value属性是紧接在这条yield之后的语句执行之后的返回值;

如果遇到的是return,那么返回对象done=truevalue则是return的返回值;

其他情况下,返回对象{done:false, value:undefined};

next的输入参数在上一次发生yield的地方返回,所以第一次调用next传入的参数是“然并卵”,next是在生成器之外调用的,所以这个机制使得我们有能力控制生成器内部的行为。

以上说了很多,先看一个用生成器实现的一个无限斐波那契数列,可以无限的调用next函数,他永远不会返回done=true

    const f = function* fibonacci() {
        let [a, b] = [0, 1];
    
        for (;;) {
            yield a;
            [a, b] = [b, a + b];
        }
    }();
    
    //执行三次,得到三个对象,其value值分别是0,1,1
    for (let i of Array(3).keys()) {
        console.log(f.next());
    }

接下来通过一段代码看看next和yield在传值和返回值上的情况,如下:

    const iter = function* gen() {
        console.log(`yield ${(yield "a" + 0)}`);
        console.log(`yield ${(yield "b" + 1)}`);
        return "c" + 2;
    }();
    
    console.log(`next:${iter.next(0).value}`);  //输出 next:a0
    console.log(`next:${iter.next(1).value}`);  //输出 yield 1 next:b1
    console.log(`next:${iter.next(2).value}`);  //输出 yield 2 next:c2

对以上代码的输出分析如下:

第一个next触发生成器执行到第一个yield,并立即执行"a" + 0 = "a0", a0作为这次next的返回值;

第二个带参数为1next触发生成器继续执行,此时第一个yield才返回1,然后执行到第二个yield并立即立即这条yield后面的"b" + 1 = "b1"b1作为这次next的返回;

第三个next执行以此类推……

异步编程方案

在同步编程模型中,每个函数总是有序依次地执行,一般上一个函数执行的结果往往是下一个函数的入参,那么在javascript中如何让下一个异步操作等待上一个异步执行得到结果之后再执行呢?

我们现在已经有了生成器并且知道next可以触发生成器执行到yield操作处,而且生成器会在遇到yield时立即执行后面的语句并暂停,那么如果yield后面是一个异步操作,而异步操作获取到结果之后再调用next不就实现了等待的效果么?

    function asyncfuc(v) {
        setTimeout(function() {
            let r = v + 20;
            console.log(r);
            g.next(r); //把异步函数执行得到的结果传出并触发下一个yield
        }, 500);
    }
    
    
    let g = function* gen() {
        let v1 = yield asyncfuc(0);
        let v2 = yield asyncfuc(v1);  //上一个异步调用的结果作为下一个异步调用的入参
        return v2;
    }();
    
    g.next();
异步操作执行链

有了前文的基础我们可以实现一个用来执行多个异步操作的函数,定义一个run(...functions)方法依次执行传入的函数,如下:

    //这个方法用来模拟一个异步调用
    function delay(time, func) {
        setTimeout(function() {
            func(`slept for ${time}`);
        }, time);
    }
    
    function run(...functions) {
        //构造一个生成器循环执行传入的方法
        let generator = function* sync(functions) {
            let result;
            for (var func of functions) {
                result = yield func(result, generator); //前一个方法执行的结果作为下一个方法的入参
            }
            return result;
        }(functions);
    
        generator.next(); //触发生成器立即执行第一个方法
    }
    
    
    //模拟异步方法调用, 斐波那契数列
    function d(result, g) {
        delay(1000, (msg) => {
            let value = result;
            if (value) {
                [value.a, value.b] = [value.b, value.a + value.b];
            } else {
                value = { a: 0, b: 1 };
            }
            console.log(value.a);
            g.next(value);
        });
        return result;
    }
    
    run(d, d, d); //顺序执行异步方法

以上实现有个值得注意的地方是业务处理函数必须带上一个生成器作为参数并在获得异步结果之后调用g.next(value),它影响了业务函数的api,因此具有比较大的侵入性。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86765.html

相关文章

  • ES6 异步编程之三:Generator

    摘要:前言在异步编程之一中实现了一个异步函数调用链,它是一个顺序调用链,很类似责任链模式,但现实往往不是平铺直叙的,更多的其实是峰回路转,本文将继续讨论更多的用法。 前言 在《ES6 异步编程之一:Generator》中实现了一个异步函数调用链,它是一个顺序调用链,很类似责任链模式,但现实往往不是平铺直叙的,更多的其实是峰回路转,本文将继续讨论更多Generator的用法。 作为函数的Gen...

    JiaXinYi 评论0 收藏0
  • JavaScript 异步

    摘要:从最开始的到封装后的都在试图解决异步编程过程中的问题。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。写一个符合规范并可配合使用的写一个符合规范并可配合使用的理解的工作原理采用回调函数来处理异步编程。 JavaScript怎么使用循环代替(异步)递归 问题描述 在开发过程中,遇到一个需求:在系统初始化时通过http获取一个第三方服务器端的列表,第三方服务器提供了一个接口,可通过...

    tuniutech 评论0 收藏0
  • es6 promise面试

    摘要:执行函数会返回一个遍历器对象,每一次函数里面的都相当一次遍历器对象的方法,并且可以通过方法传入自定义的来改变函数的行为。函数可以通过配合函数更轻松更优雅的实现异步编程和控制流管理。它和构造函数的不同点类的内部定义的所有方法,都是不可枚举的。 let const的命令 在ES6之前,声明变量只能用var,var方式声明变量其实是很不合理的,准确的说,是因为ES5里面没有块级作用域是很不合...

    timger 评论0 收藏0
  • 谈谈ES6前后的异步编程

    摘要:回调函数这是异步编程最基本的方法。对象对象是工作组提出的一种规范,目的是为异步编程提供统一接口。诞生后,出现了函数,它将异步编程带入了一个全新的阶段。 更多详情点击http://blog.zhangbing.club/Ja... Javascript 语言的执行环境是单线程的,如果没有异步编程,根本没法用,非卡死不可。 为了解决这个问题,Javascript语言将任务的执行模式分成两种...

    fizz 评论0 收藏0
  • ES6&ES7中的异步Generator函数与异步编程

    摘要:传统的异步方法回调函数事件监听发布订阅之前写过一篇关于的文章,里边写过关于异步的一些概念。内部函数就是的回调函数,函数首先把函数的指针指向函数的下一步方法,如果没有,就把函数传给函数属性,否则直接退出。 Generator函数与异步编程 因为js是单线程语言,所以需要异步编程的存在,要不效率太低会卡死。 传统的异步方法 回调函数 事件监听 发布/订阅 Promise 之前写过一篇关...

    venmos 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<