ES6(十四)—— Generator

目录

  • Generator是做什么的
  • ES6如何让遍历“停”下来
  • Basic Syntax —— 基础语法
  • Senior Syntax —— 高级语法
    • next添加参数
    • return控制结束
    • throw抛出异常控制
  • Generator异步方案
  • 案例
    • 抽奖
    • 数3的倍数小游戏
    • 使用Generator函数实现Iterator方法
  • ES6-ES10学习版图

Generator是做什么的

  1. 控制循环流程用的
  2. 最重要的作用是解决异步编程嵌套层级较深的问题。

ES6如何让遍历“停”下来

ES5循环一旦执行,无法停下来的

function loop() {
  for(let i = 0; i < 5; i++) {
    console.log(i)
  }
}

loop()
// 0
// 1
// 2
// 3
// 5

使用Generator,怎么改造?

// 修改一,在loop前面加一个星号
function * loop() {
  for(let i = 0; i < 5; i++) {
  // 修改二:在输出前面加yield
    yield console.log(i)
  }
}
// 修改三:定义一个变量将loop赋值给l
const l = loop()

// 这个时候并没有输出,若要输出调用next方法
l.next() // 0
l.next() // 1
l.next() // 2
l.next() // 3
l.next() // 4
l.next() // 之后不会输出任何东西

//应用场景:年会抽奖、自定义遍历器

Basic Syntax —— 基础语法

  1. 遍历器就是一个函数,但是与普通的函数不同,形式上多了一个*
  2. 函数内部可以使用yield停下来
  3. 调用的时候不会立即执行而是返回一个生成器对象
  4. 返回的生成器对象调用next控制循环
  5. Generator函数的定义不能使用箭头函数,否则会出发报错SyntaxError
function * gen() {
  let val
  val = yield 1
  console.log(val)
}

const l = gen()
// "Generator { }"

l.next() // 没有任何输出
l.next() // undefined  yield表达式没有返回值,所以返回undefined
  1. next()的返回值
  • 第一个参数是返回的值,

  • 第二个参数:done属性,表示是否遍历完成,false是没有遍历完,true是遍历完成

  1. 再执行一次next()方法会继续执行
function * gen() {
  let val
  val = yield [1, 2, 3]
  console.log(val) // undefined
}

const l = gen()

console.log(l.next()) // {value: Array(3), done: false}
console.log(l.next()) // {value: undefined, done: true}
function * gen() {
  let val
  // yield 后面加了一个星号,后面是一个遍历的对象,所以可以嵌套一个Generator对象
  val = yield * [1, 2, 3]
  console.log(val) // undefined
}

const l = gen()

console.log(l.next()) // {value: 1, done: false}
console.log(l.next()) // {value: 2, done: false}

学到这里要明白:

  1. yield有没有返回值?

    没有,但是遍历器对象的next方法可以修改这个默认值

  2. ES5相比,是如何控制程序的停止和启动的?

    使用yield去控制停止,使用next去控制启动

Senior Syntax —— 高级语法

如何在函数外部控制函数内部的运行?

next添加参数

next函数写参数,作为yield的返回值

function * gen() {
  let val
  val = yield [1, 2, 3]
  console.log(val) // 20
}

const l = gen()

console.log(l.next(10))// {value: Array(3), done: false}
// 此时yield没有赋值,所以10并没有用
console.log(l.next(20))// {value: undefined, done: true}
// 此时yield对val进行赋值操作,yield表达式的值是20

讲义的例子可以理解更深刻

function * gen() {
  var val = 100
  while(true){
    console.log(`before${val}`)
    val = yield val
    console.log(`return ${val}`)
  }
}

let g = gen()
console.log(g.next(20).value)
// before 100
// 100
console.log(g.next(30).value)
// return 30
// before 30
// 30
console.log(g.next(40).value)
// return 40
// before 40
// 40

1.g.next(20) 这句代码会执行 gen 内部的代码,遇到第一个 yield 暂停。所以 console.log("before "+val) 执行输出了 before 100,此时的 val100,所以执行到 yield val 返回了 100,注意 yield val 并没有赋值给 val

2.g.next(30) 这句代码会继续执行 gen 内部的代码,也就是 val = yield val 这句,因为 next 传入了 30,所以 yield val 这个返回值就是 30,因此 val 被赋值 30,执行到console.log("return "+val)输出了 30,此时没有遇到 yield 代码继续执行,也就是 while 的判断,继续执行console.log("before "+val) 输出了 before 30,再执行遇到了yield val程序暂停。

3.g.next(40) 重复步骤 2

return控制结束

function * gen() {
  let val
  val = yield [1, 2, 3]
  console.log(val) // 没有执行
}

const l = gen()

console.log(l.next(10))// {value: Array(3), done: false}
console.log(l.return())// {value: undefined, done: true}
//返回操作,函数终止
console.log(l.next(20))// {value: undefined, done: true}

添加返回值的参数

function * gen() {
  let val
  val = yield [1, 2, 3]
  console.log(val) // 没有执行
}

const l = gen()

console.log(l.next(10))// {value: Array(3), done: false}
console.log(l.return(100))// {value: 100, done: true}
//返回操作,函数终止
console.log(l.next(20))// {value: undefined, done: true}

throw抛出异常控制

function * gen() {
  while (true) {
    try {
      yield 1
    } catch (e) {
      console.log(e.message) // ss
    }
  }
}

const l = gen()

console.log(l.next())//{value: 1, done: false}
console.log(l.next())//{value: 1, done: false}
console.log(l.next())//{value: 1, done: false}

l.throw(new Error('ss')) 
// 抛出错误,执行catch
console.log(l.next()) //{value: 1, done: false}

Generator异步方案

之前说过最重要的作用是解决异步编程嵌套层级较深的问题,那我们来看一下,即使使用了Promise没有了大量的嵌套代码,但是依然有大量的回调函数,可读性依然不好。

// Promise chain
ajax('/api/url1')
    .then(value => {
        return ajax('ajax/url2')
    })
    .then(value => {
        return ajax('ajax/url3')
    })
    .then(value => {
        return ajax('ajax/url4')
    })
    .catch(error => {
        console.error(error)
    })

解决上面的问题,就要用到Generator生成器函数,有一个更加完善的库是co库,可以看看 阮一峰对co函数库的解释 ,不过后来有了async和await之后,就很少使用了。下面来看一个例子:

// 定义一个generator函数,ajax返回一个promise对象
function * main () {
    try{
        const users = yield ajax('/api/users.json')
        console.log(users)
        
        const posts = yield ajax('/api/posts.json')
        console.log(posts)
        
        const urls = yield ajax('/api/urls.json')
        console.log(urls)
    } catch (e) {
        //捕获异常
        console.log(e)
    }
}

const g = main()

// 定义一个递归函数
function handlerResult(result) {
    if(result.done) return ///如果为true,退出递归调用
    // result.value返回是一个promise对象,使用then可以执行其结果
    result.value.then(data => {
        //g.next(data)可以作为yield返回值,再进入下一次递归
        handlerResult(g.next(data))
    // 异常逻辑
    }, error => {
        g.throw(error)
    })
}

handleResult(g.next())

案例

抽奖

ES5

function draw (first = 1, second = 3, third = 5) {
  // 三个奖的候选人,一个结果,一个随机数
  let firstPrize = ['1A', '1B', '1C', '1D', '1E']
  let secondPrize = ['2A', '2B', '2C', '2D', '2E', '2F', '2G', '2H', '2I', '2J', '2K', '2L']
  let thirdPrize = ['3A', '3B', '3C', '3D', '3E', '3F', '3G', '3H', '3I', '3J', '3K', '3L', '3M', '3N', '3O', '3P', '3Q', '3R', '3S', '3T', '3U', '3V', '3W', '3X', '3Y', '3Z']
  let result = []
  let random
  // 抽一等奖
  for(let i = 0; i < first; i++){
    random = Math.floor(Math.random() * firstPrize.length)
    result = result.concat(firstPrize.splice(random, 1))
  }
  // 抽二等奖
  for(let i = 0; i < second; i++){
    random = Math.floor(Math.random() * secondPrize.length)
    result = result.concat(secondPrize.splice(random, 1))
  }
  // 抽三等奖
  for(let i = 0; i < third; i++){
    random = Math.floor(Math.random() * thirdPrize.length)
    result = result.concat(thirdPrize.splice(random, 1))
  }
  return result
}

console.log(draw())
// ["1A", "2D", "2K", "2A", "3A", "3G", "3Y", "3W", "3P"]

ES6

function * draw (first = 1, second = 3, third = 5) {
  // 三个奖的候选人,一个结果,一个随机数
  let firstPrize = ['1A', '1B', '1C', '1D', '1E']
  let secondPrize = ['2A', '2B', '2C', '2D', '2E', '2F', '2G', '2H', '2I', '2J', '2K', '2L']
  let thirdPrize = ['3A', '3B', '3C', '3D', '3E', '3F', '3G', '3H', '3I', '3J', '3K', '3L', '3M', '3N', '3O', '3P', '3Q', '3R', '3S', '3T', '3U', '3V', '3W', '3X', '3Y', '3Z']
  let count = 0
  let random
  while(1){
    if (count < first) {
      random = Math.floor(Math.random() * firstPrize.length)
      yield firstPrize[random]
      count ++
      firstPrize.splice(random, 1)
    } else if (count < first + second) {
      random = Math.floor(Math.random() * secondPrize.length)
      yield secondPrize[random]
      count ++
      secondPrize.splice(random, 1)
    } else if (count < first + second + third) {
      random = Math.floor(Math.random() * thirdPrize.length)
      yield thirdPrize[random]
      count ++
      thirdPrize.splice(random, 1)
    } else {
      return false
    }
  }
  
}

let d = draw()
console.log(d.next().value) // 1C
console.log(d.next().value) // 2E
console.log(d.next().value) // 2H
console.log(d.next().value) // 2C
console.log(d.next().value) // 3H
console.log(d.next().value) // 3V
console.log(d.next().value) // 3A
console.log(d.next().value) // 3J
console.log(d.next().value) // 3N
console.log(d.next().value) // false
console.log(d.next().value) // undefined
console.log(d.next().value) // undefined

数3的倍数小游戏

如果是ES5,是无限死循环,程序崩溃

ES6

function * count (x = 1) {
  while (1) {
    if (x % 3 === 0) {
      yield x
    }
    x++
  }
}

let num = count()
console.log(num.next().value) // 3
console.log(num.next().value) // 6
console.log(num.next().value) // 9
console.log(num.next().value) // 12
console.log(num.next().value) // 15
console.log(num.next().value) // 18
...

使用Generator函数实现Iterator方法

const todos = {
  life: ['吃饭', '睡觉', '打豆豆'],
  learn: ['语文', '数学', '外语'],
  work: ['喝茶'],

  each: function (callback) {
    const all = [].concat(this.life, this.learn, this.work)
    for( const item of all) {
      callback(item)
    }
  },
  [Symbol.iterator]: function * () {
    const all = [...this.life, ...this.learn, ...this.work]
    for(const item of all) {
      yield item
    }
  }
}

for(const item of todos){
  console.log(item)
}
// 吃饭
// 睡觉
// 打豆豆
// 语文
// 数学
// 外语
// 喝茶

学习版图

image
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270