异步解决方案——Generator 函数(了解异步编程,Generator 执行机制、next传参)

ES6 Generator 函数

1. 异步编程

Javascript 的执行环境是“单线程”的,如果没有异步编程,根本无法使用,容易造成卡死。

什么是异步呢?

所谓“异步”,简单来说就是一个任务不是连续完成的,该任务被人为分成两段。先执行第一段,然后转而执行其他任务,等做好准备后再回过头执行第二段

比如,有一个任务是读取文件进行处理,任务的第一段是向操作系统发出请求,要求读取文件。然后,程序执行其他任务,等到操作系统返回文件后再接着执行任务的第二段(处理文件)这种不连续的执行就叫做异步

相应地,连续执行叫作同步由于是连续执行,不能插入其他任务,所以操作系统从硬盘读取文件的这段时间,程序只能等待


在Generator函数出现前,异步编程的方法大概有4种:

  • 回调函数
  • 事件监听
  • 发布/订阅
  • Promise对象

Promise对象是为了解决回调地狱而出现的。它不是新的语法功能,而是一种新的写法,允许将回调函数的嵌套改写成链式调用

Promise的写法只是回调函数的改进,使用 then 方法以后,异步任务的两段执行更清楚了。其最大问题是代码冗余,原来的任务被 Promise包装之后,无论什么操作,一眼看去都是许多 then 的堆积,原来的语义变得很不清楚。

在这个背景下,Generator函数出现了!


Generator 函数最大特点就是可以交出函数的执行权(即暂停执行)。

整个 Generator 函数就是一个封装的异步任务,或者说是异步任务的容器 ,异步操作需要暂停的地方都用 yield语句注明。

yield命令表示执行到此处时,执行权交给其他协程。
也就是说,yield命令是异步两个阶段的分界线。协程遇到yield命令就暂停,等到执行权返回,再从暂停的地方继续往后执行。

什么是异步任务? 在下面这个例子中,A就是异步任务,因为分成两段(或以上)执行。

在这里插入图片描述


2. Generator 函数

1.Generator 有两个区分于普通函数的部分:

  • 在 function 后面,函数名之前有个 **用来表示函数为 Generator 函数
  • 函数内部有 yield 表达式yield 用来定义函数内部的状态。
function* func(){
 console.log("one");
 yield '1';
 console.log("two");
 yield '2'; 
 console.log("three");
 return '3';
}

2.执行机制

  • 调用 Generator 函数和调用普通函数一样,在函数名后面加上()即可
  • 但是 Generator 函数不会像普通函数一样立即执行,而是 返回一个指向内部状态对象的指针Iterator ,所以要调用遍历器对象Iterator 的 next 方法,指针就会从函数头部或者上一次停下来的地方开始执行。

示例:

  • 第一次调用 next 方法时,从 Generator 函数的头部开始执行先是打印了 one ,执行到 yield 就停下来,并将yield 后边表达式的值 ‘1’,作为返回对象的 value 属性值,此时函数还没有执行完, 返回对象的 done 属性值是 false。

  • 第二次调用 next 方法时,同上步 。

  • 第三次调用 next 方法时,先是打印了 three ,然后执行了函数的返回操作,并将 return 后面的表达式的值,作为返回对象的 value 属性值,此时函数已经结束,多以 done 属性值为true 。

注意:若再次调用 next方法时, 此时函数已经执行完了,所以返回 value 属性值是 undefined ,done 属性值是 true

function* func(){
    console.log("one");
    yield '1';
    console.log("two");
    yield '2'; 
    console.log("three");
    return '3';
}
let iterator = func() // 调用 Generator函数,函数并没有执行,返回的是一个Iterator对象
console.log(iterator.next()); 
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

在这里插入图片描述
3.一般情况下

  • next 方法不传入参数的时候,yield 表达式的返回值是 undefined
function* func(){
    console.log("start");
    console.log("---------------------------------");
    var x = yield '2';
    console.log("one:" + x);
    console.log("---------------------------------");
    var y = yield '3';
    console.log("two:" + y);
    console.log("total:" + (x + y));
    console.log("---------------------------------");
}
let iterator = func()   // 调用 Generator函数,函数并没有执行,返回的是一个Iterator对象
console.log(iterator.next()); 
console.log(iterator.next());
console.log(iterator.next());

在这里插入图片描述

  • next 传入参数的时候,该参数会作为上一步yield的返回值。
function* func(){
    console.log("start");
    console.log("---------------------------------");
    var x = yield '2';
    console.log("one:" + x);
    console.log("---------------------------------");
    var y = yield '3';
    console.log("two:" + y);
    console.log("total:" + (x + y));
    console.log("---------------------------------");
}
let iterator = func()   // 调用 Generator函数,函数并没有执行,返回的是一个Iterator对象
console.log(iterator.next(10)); 
console.log(iterator.next(20));
console.log(iterator.next(30));

在这里插入图片描述
再来看一个例子,

function *g(a){
     const b=2*(yield(a-1))
     // b=12
     const c=yield(b/4)
     // c=4
     return a-b+c
     // a-b+c=2-12+4=-6
 }
 const o=g(2) 
 console.log(o.next());  // {value:1,done:false}
 console.log(o.next(6)); // {value:3,done:false}
 console.log(o.next(4)); //{value:-6,done:true}

首先调用g(2)得到的是一个内部指针(遍历器)。

  • 调用第一个next(),返回值是一个对象。其中value:1是第一个yield计算的结果a-1=2-1=1
  • 再调下一个next(6),这个6会替换掉上一次停下来的yield表达式。所以b=2*6=12

剩下的可以类似分析
在这里插入图片描述
4. 我们可以使用for循环遍历输出Generator 函数。

function* g(){
    console.log("one");
    yield '1';
    console.log("two");
    yield '2'; 
    console.log("three");
    yield '3';
} 

for(let v of g()){
    console.log(v);
}

在这里插入图片描述
还有使用yield* 表达式在一个Generator 函数中调用另外一个Generator 函数

function* g1(){
    yield '1';
    yield '2'; 
    yield '3';
} 

function* g2(){
    yield '11';
    yield '22'; 
    yield* g1();
    yield '33';
} 

for(let v of g2()){
    console.log(v);
}

在这里插入图片描述

再来一个例子说明正确使用yield* 表达式在一个Generator 函数中调用另外一个Generator 函数

function* g1(){
            yield 'one';
            yield 'two'
        } 

        function* g2(){
            yield '11';
            yield* g1();
            yield '22';
        } 
        
        const o=g2(2) 
        console.log(o.next());  // {value:11,done:false}
        console.log(o.next()); // {value:one,done:false}
        console.log(o.next()); //{value:two,done:false}

在这里插入图片描述

对于这个例子,如果yield* g1();写为yield g1();那会得到什么呢。结果如图所示,第二个next只能得到一个遍历器。
在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

焦妮敲代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值