资讯专栏INFORMATION COLUMN

ES6&ES7中的异步之Generator的语法

djfml / 386人阅读

摘要:第二次同理,遇到了第二个函数会停下来,输出的遍历器对象值为,的值依然是。比如返回的遍历器对象,都会有一个方法,这个方法挂在原型上。这三个函数共同的作用是让函数恢复执行。

Generator的语法

generator的英文意思是生成器

简介

关于Generator函数,我们可以理解成是一个状态机,里面封装了多种不同的状态。

function* gener(){
    yield "hello";
    yield "world";
    return "ending"; 
}

var g = gener();   // g是一个遍历器对象
g.next();   // {value:"hello",done:false}
g.next();   // {value:"world",done:false}
g.next();   // {value:"ending",done:true}
g.next();   // {value:undefined,done:true}

上面代码定义了一个Generator函数,这个函数有两个地方与其他函数不同

function后边跟一个*

函数内部有一个关键字yield。yield表示定义一个状态,所以上边的函数其实有三个状态。

调用Generator函数和普通函数一样,都是后面跟圆括号,不过调用Generator函数,函数并不执行,返回的也不是正常的返回结果,而是一个指向内部状态的指针函数,也就是Iterator这个遍历器对象。

来剖析一下上面的代码:

上面代码一共执行了四次。调用next()便会执行一次。每次代码执行到yield的时候就会停下来,输出一个对象,对象的value值就是yield后边的值,done这个值表示遍历是否结束,这个时候为false。

第二次同理,遇到了第二个yield函数会停下来,输出的遍历器对象value值为"world",done的值依然是false。

第三次,当代码遇到了return语句(如果没有return语句,就一直执行到函数结束)这个时候返回的遍历器对象的value的值,就是return后边跟的表达式的值,这个时候因为遍历结束了,所以done的值就变成了true.

第四次,因为函数已经return,next()返回的对象的value就是undefined,done值为true

yield的逻辑

理论上,yield提供了一种函数可以暂停的机制。而暂停的表达式就是yield。

当代吗执行到yield语句的时候,会暂停不会立即执行,并且把yield后面表达式的值当做返回对象的value属性值返回。

当下一次调用next()方法的时候,会从当前暂停的位置继续执行,知道遇到下一个yield或者return语句,返回其后面表达式的值。

如果都没有,那么一直执行到代码结束。这个时候返回对象value值是undefined。

当遍历结束的时候,done的值会从false变成true.

yield和return的区别?

两者相同点:都是返回跟在其后面的表达式的值。

两者不同点:yield有记忆功能,函数执行完以后会记录下来在从记录的位置继续执行;return并不具有记忆功能,从这返回以后函数不会在执行,仅仅执行一次。而Generator可以返回多个值,返回一系列的值。

function* fun(){
    console.log("执行了!")
}

var gen = fun();
setTimeout(function(){
    gen.next();
},3000)

这个console要到3s后才执行。

另外,yield语句只能在Generator函数里面。所以,yield也不能放到forEach函数里,也不能放到map函数里。

function funerr(){
    yield 123+321
}   //报错

再另外,如果yield语句在另外一个表达式里,必须在圆括号里。

function* fun(){
    console.log("hello"+yield)   // 语法错误
    console.log("hello"+(yield))   //正确
}

再再另外,yield作为函数参数或者赋值表达式的右边,可以不用加括号。

next方法的参数

yield本身并没有返回值,但是在next的参数里可以带一个参数,这个参数表示上一个yield语句的返回值。

//  next()的参数
function *f() {
  for(var i=0;true;i++){
    var reset = yield i;
    if(reset) {
      i = -1
    }
  }
}

var f = f()
console.log(f.next())       //{value:0,done:false}
console.log(f.next())       //{value:1,done:false}
console.log(f.next())       //{value:2,done:false}
console.log(f.next(true))   //{value:0,done:false} 

前边几次输出,这个时候reset的值是undefined,所以,i的值一次增加,当next函数传一个true,代表上一次的yield的值是true,那么这个时候i的值是-1,下一次循环从i等于-1开始.

Generator函数,从暂停状态到恢复运行,上下文是不变的。通过next方法的参数,就有办法在函数运行之后重新往函数里注入值,也就是在函数不同阶段注入不同的值,

function* foo(x) {
  var y = yield (x+2)
  var z = (yield ((y+3) *2))
  return x+y+z
}

var foo1 = foo(5)
console.log(foo1.next())     //{value:7,done:false}
console.log(foo1.next())     //{value:NaN,done:false}
console.log(foo1.next())     // {value:NaN,done:true}

var foo2 = foo(5) 
console.log(foo2.next())     // {value:7,done:false}   x:5 y:7
console.log(foo2.next(2))    //{value:10,done:false}   x:5 y:2
console.log(foo2.next(3))    // {value:10,done:true}   x:5 y:2 z:3

执行第二个next方法的时候,这个时候没有传入值,所以这个时候,y的值是undefined。所以。2 * undefined是NaN。

这个地方不是太好理解,可以再举一个例子

function* foo(x) {
  var y = 2 * (yield (x+2))    //之前说过的,yield作为表达式一定要在括号里
  var z =  (yield (y+3) *2)
  return x+y+z
}

var foo2 = foo(5)
log(foo2.next())    
log(foo2.next(2))  
log(foo2.next(3))  
用for...of代替next方法

每次总是调用next方法太过麻烦。for...of循环可以自动遍历Generator函数生成的Itearator对象,不需要调用next方法。

function* bar () {
  yield 3;
  yield 4;
  yield 1;
  yield 7;
  yield 9;
  return 0;
}

for (var i of bar()){
  console.log(i)    // 3,4,1,7,9
}

注意,上面的return语句并不在循环中,因为遍历到done为true的时候就会停止,所以,不会输出0.

理论上,实现了Iterator遍历器接口的扩展运算符(...),结构赋值,Array.from()内部调用的,都可以将Generator的返回值作为参数。比如:

function* foo(){
    yield:1;
    yield:3;
    return 9;
    yield:6
}

[...foo()]    // [1,3]
Array.from(foo())  // [1,2]
let [x,y] = foo()   //x=1 y=2
Generator.prototype.throw()

Generator返回的遍历器对象,都会有一个throw方法,这个方法挂在原型上。作用是在外部抛出在函数内部捕获的错误

function* gg() {
  try {
    yield ;
  } catch (e){
    console.log("内部错误",e)
  }
}

var gg = gg()
gg.next();

try {
  gg.throw("a")
  gg.throw("b")
} catch (e){
  console.log("外部错误",e)
}

在外部的try语句中,会连续抛出两个错误,第一个错误会被内部捕获,但是到了第二次的时候,因为内部的catch语句已经执行过了,所以就不会再次执行,所以错误会被外部的catch捕获。throw方法接受一个参数,可以再catch里输出,不过一般还是catch输出一个Error对象。

var g = function* () {
  try {
    yield 8;
  } catch (e) {
    console.log(e);
  }
};

var i = g();
console.log(i.next())
i.throw(new Error("出错了!"));
console.log(i.next(7))

如果在Generator函数内部没有部署try/catch,那么直接会在外部抛出错误,如果内外部都没有try/catch。那么函数直接错误中断执行。

如果函数内部抛出了错误,并不影响接下来yield或者return的执行。

Generator.prototype.return()

这个函数的作用就是return出一个值,并且终结Generator函数的执行。

function* gen() {
  yield 1;
  yield 2;
  yield 3;
}

var g = gen();

g.next()        // { value: 1, done: false }
g.return("foo") // { value: "foo", done: true }
g.next()        // { value: undefined, done: true }
next() throw() return()

这三个函数共同的作用是让Generator函数恢复执行。

next()就是让yield赋一个值,如果next()有参数,即是给yield传入一个值,如果没有参数,就是undefined。
throw()是把yield表达式替换成一个throw语句。
return()是把yield表达式替换成一个return语句。

理论上,三个函数都是在做同样一件事情。

yield* 表达式

如果在一个Generator函数里调用另外一个Generator函数,默认是没有效果的。

function* foo(){
    yield "xx";
    yield "yy";
}

function* bar(){
    yield "aa";
    foo();
    yield "bb";
}

for(var i of bar()){
    console.log(i)        // aa bb
}

如果想要在bar函数中执行foo函数,需要改写一下bar函数

function bar(){
    yield "aa";
    yield* foo()
    yield "bb"
}

输出的会是"aa" "xx" "yy" "bb"
作为对象属性的Generator函数

如果一个对象的属性是Generator函数,可以简写成:

let obj = {
    * foo(){
        ....
    }
}

//等价于

let obj = {
    foo : function* (){
        ....
    }
}
Generator中的this

Generator函数返回一个遍历器对象,这个遍历器对象是Generator函数的实例,当然也就继承了函数原型上的那些方法。

function* foo(){
    yield "xx"
}

let f = foo()
f00.prototype.hello = function(){
    console.log(123)
}

f instanceof foo // true
f.hello()   //123

所以,代码可以看出,f是foo的实例,同时可以调用foo原型上重写的方法。

但是:

function* foo(){
    this.a = 10
}

let f = foo()
f.a  //undefined

new foo()   //报错,foo is not a constructor

因为foo()返回的是一个遍历器对象,而不是this.
同时,Generator函数也不能和new一起使用。

那如何既能调用next()又能获取this呢。有一个变通的方法。用call绑定内部的this

function* foo(){
    this.a = 1;
    yield this.b = 2;
    yield this.c = 3;
}

let f = foo.call(F.prototype)

f.next()  {value:2,done:false}
f.next()  {value:3,done:true}
f.next()  {valye:undefined,done:true}

f.a  1
f.b  2
f.3  3

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

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

相关文章

  • ES6&ES7异步async函数

    摘要:更好的语义和分别表示异步和等待,比起和更容易理解。前边声明关键字,表示内部有内部操作,调用函数会返回一个对象。等价于其中函数就是自动执行器。 async函数 定义 async函数其实就是之前说过的Generator的语法糖,用于实现异步操作。它是ES2017的新标准。 读取两个文件: const fs = require(fs) const readFile = function(f...

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

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

    venmos 评论0 收藏0
  • Promise & Generator——幸福地用同步方法写异步JavaScript

    摘要:在这里看尤雨溪大神的这篇小短文,非常精简扼要地介绍了当前常用的。根据尤雨溪大神的说法,的也只是的语法糖而已。对象有三种状态,,。对象通过和方法来规定异步结束之后的操作正确处理函数错误处理函数。方便进行后续的成功处理或者错误处理。 最近在写一个自己的网站的时候(可以观摩一下~Colors),在无意识中用callback写了一段嵌套了5重回调函数的可怕的代码。回过神来的时候被自己吓了一跳,...

    Harpsichord1207 评论0 收藏0
  • ES6-7

    摘要:的翻译文档由的维护很多人说,阮老师已经有一本关于的书了入门,觉得看看这本书就足够了。前端的异步解决方案之和异步编程模式在前端开发过程中,显得越来越重要。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。 JavaScript Promise 迷你书(中文版) 超详细介绍promise的gitbook,看完再不会promise...... 本书的目的是以目前还在制定中的ECMASc...

    mudiyouyou 评论0 收藏0
  • ES6 Generator异步同步书写

    摘要:返回值是一个对象,它的第一个属性是后面表达式的值或者的值第二个属性表示函数是否执行完成。真正的业务逻辑确实是用同步的方式写的。 开始前 我们从来没有停止过对javascript语言异步调用方式的改造,我们一直都想用像java那样同步的方式去写异步,尽管Promise可以让我们将异步回调添加到then方法中,但是这种调用方式仍然不那么优雅,es6 中新增加了generator,我们可以通...

    andycall 评论0 收藏0

发表评论

0条评论

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