今天在学习redux-saga时,外部链接推荐了这篇文章ES6 generators in depth,所以翻译的同时也可以加深一下对Generator的理解。

这里对原文一些只能在高版本现代浏览器使用的API进行了替换。

1. 概述

Generator有两个很重要的应用:

  • 实现迭代器
  • 在异步调用后阻塞

1.1 借助generator实现迭代器

下面的函数将返回一个可以遍历一个对象属性的迭代器,每个属性对应一个[key, value]对:

function* objectEntries(obj) {
let propKeys = Object.keys(obj)
for(let i = 0, len = propKeys.length; i < len ; i++) {
yield [propKeys[i], obj[propKeys[i]]]
}
} let jane = {first: 'Jane', last: 'Doe'};
const objectIter = objectEntries(jane)
let item = objectIter.next()
while(!item.done) {
const temp = item.value
console.log(`key: ${temp[0]}, value: ${temp[1]}`)
item = objectIter.next()
}

结果为:

1.2 在异步调用后阻塞

下面代码异步的获取两个JSON文件,后面代码会被阻塞,等两个请求都返回时再继续执行。

const axios = require('axios')

function* request() {
try {
let promise = yield Promise.all([
axios.get('https://zan.wilddogio.com/antd/select.json')
.then((response) => response.data),
axios.get('https://zan.wilddogio.com/antd/select.json')
.then((response) => response.data)
])
promise.then(result => {
console.log(`first: ${result[0][0].label}`)
console.log(`second: ${result[1][0].value}`)
}) } catch(e) {
console.log('Failure to read: ' + e)
}
} const gen = request()
const item = gen.next()
gen.next(item.value)

结果如下:

2. 什么是Generator

Generator是一个可以暂停和恢复的函数,这里举一些场景:

第一个例子假设有这样一个generator函数:

function* genFunc() {
console.log('First')
yield console.log('pause')
console.log('Second')
}

genFunc有以下两点区别于一般函数:

  • function后跟着一个*
  • 可以通过yield暂停

调用genFunc不会执行它,它会返回一个称之为generator对象,它可以用来控制genFunc执行:

const g = genFunc()

genFunc()会挂起在执行体开始处,调用g.next()继续执行genFunc,

g.next()

结果如下:



可以看到在打印出pause后就停止了。

继续调用g.next()

g.next()

结果Second被打印出来。

2.1 创建generator的方法

  1. 通过generator函数声明
function* genFunc() {}
  1. 通过generator函数表达式声明
const genFunc = function* (){}
  1. 将generator函数定义在对象字面量中
const obj = {
* genFunc() {}
}

4 作为一个成员函数(类函数)定义在class中

class MyClass{
* genFunc() {}
}

2.2 generator所扮演的角色

generator可以扮演以下角色:

  1. 迭代器(data producers):每一个yield可以通过next()返回一个value,这意味generator可以从循环或递归中有序的获取一个序列。由于generator对象实现了Iterable接口,这些序列可以被支持ES6 iterables的构造函数所处理(翻译可能不准确需要修改原文为these sequences can be processed by any ECMAScript 6 construct that supports iterables.),比如你可以直接使用for-of和展开操作符...来直接处理这个序列。

  2. Observer(观察者 - 数据消费者):yield也可以获取next()传进来的参数,这意味着generator又变成了一个数据消费者。

  3. 协程(Coroutines - 数据生产者与消费者):(Given that generators are pausable and can be both data producers and data consumers, not much work is needed to turn them into coroutines (cooperatively multitasked tasks).

下面会详细介绍这些角色到底是什么。

3 Generator迭代器(数据生产者)

在解释之前,generator对象既可以生产数据也可以消费数据,这节就阐述如何生产数据,generator实现了IterableIterator接口,再次说明了generator函数即是可迭代的,也是迭代器。

interface Iterable {
[Symbol.iterator]() : Iterator;
} iterface Iterator {
next(value? : any) : IteratorResult;
} iterface IteratorResult {
value : any;
done : boolean
}

3.1 应用generator迭代器的方法

假设我们有这个一个generator函数

function* genFunc() {
yield 'a';
yield 'b';
}

以下三种是非常重要的用法:

第一个for-of

for (let x of genFunc()) {
console.log(x)
}
// Output:
// a
// b

第二个, 展开(...)操作符:

let arr = [...genFunc()]; // ['a', 'b']

第三个解构

> let [x, y] = genFunc();
> x
'a'
> y
'b'

3.2 来自generator的返回值

这块比较容易理解直接跳过了^ ^

3.3 应用generator迭代器的例子

这里我们来完成一个类似for-of功能函数forOf

function forOf(obj) {
const objSymbol = Object.getOwnPropertySymbols(obj)[0]
const o = obj[objSymbol]
let index = 0;
const keys = Object.keys(o) return {
[objSymbol]() {
return this
},
next() {
if(index < keys.length) {
let key = keys[index]
index++
return {value: o[key], done: false}
} else {
return {
value: undefined,
done: true
}
}
}
}
} const obj = {
[Symbol('generator')]: {
name: 'gen',
age: 18
}
} const g = forOf(obj)
let iter = g.next()
while(!iter.done) {
console.log(iter.value)
iter = g.next()
}

结果如下:

3.4 通过yield*递归输出

yield*操作符可以在一个generator函数中调用另一个generator函数,我们先来一个简单的例子来说明如果产生数据,后面我们再解释如何加入输入。

我们提取一个公共遍历打印函数

exports.travel = function (genFunc) {
let idx = 0
const g = genFunc()
let iter = g.next()
while(!iter.done) {
console.log(`${idx}: ${iter.value}`)
idx++
iter = g.next()
}
}
const {travel} = require('./utils')

function* foo() {
yield 'a'
yield 'b'
} function* bar() {
yield 'x'
foo()
yield 'y'
} travel(bar)

结果如下:

可以看到foo()并没有任何输出,这时候需要yield*出场了。

function* bar() {
yield 'x'
yield* foo()
yield 'y'
}

结果如下:

基本上,yield*就是这样工作的

function* bar() {
yield 'x'
for(let value of foo()) {
yield value
}
yield 'y'
}

当然yield不仅仅可以作用在generator函数上,任意可遍历的集合都是可以的:

数组

function* bla() {
yield 'sequence'
yield* ['of', 'yield']
yield 'values'
} travel(bla)

结果如下:

对象

const obj = {
* [Symbol.iterator]() {
const keys = Object.keys(this)
for(let i = 0, len = keys.length; i < len; i++) {
yield this[keys[i]]
}
}
} obj.name = 'gen'
obj.age = 18 for(let a of obj) {
console.log(a)
}

ES6 generators in depth 一(译)的更多相关文章

  1. ES6 Generators并发

    ES6 Generators系列: ES6 Generators基本概念 深入研究ES6 Generators ES6 Generators的异步应用 ES6 Generators并发 如果你已经读过 ...

  2. ES6 Generators的异步应用

    ES6 Generators系列: ES6 Generators基本概念 深入研究ES6 Generators ES6 Generators的异步应用 ES6 Generators并发 通过前面两篇文 ...

  3. 深入研究ES6 Generators

    ES6 Generators系列: ES6 Generators基本概念 深入研究ES6 Generators ES6 Generators的异步应用 ES6 Generators并发 如果你还不知道 ...

  4. ES6 Generators基本概念

    ES6 Generators系列: ES6 Generators基本概念 深入研究ES6 Generators ES6 Generators的异步应用 ES6 Generators并发 在JavaSc ...

  5. Iterators & Generators in depth

    Iterators & Generators in depth https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/It ...

  6. [ES6] Generators

    Example 1: function *topicList(){ yield "ES2015"; yield "Semi-colons: good or bad?&qu ...

  7. [转] Understanding JavaScript’s async await

    PS:Promise的用处是异步调用,这个对象使用的时候,call then函数,传一个处理函数进去,处理异步调用后的结果 Promise<Action>这样的对象呢,异步调用后的结果是一 ...

  8. ES6深度解析3:Generators

    介绍ES6 Generators 什么是Generators(生成器函数)?让我们先来看看一个例子. function* quips(name) { yield "hello " ...

  9. 用简单的方法学习ES6

    ES6 简要概览 这里是ES6 简要概览.本文大量参考了ES6特性代码仓库,请允许我感谢其作者@Luke Hoban的卓越贡献,也感谢@Axel Rauschmayer所作的[优秀书籍]//explo ...

随机推荐

  1. 项目(七)SVN版本管理与大型代码上线方案

    SVN介绍 Svn(subversion)是近年来崛起的非常优秀的版本管理工具,与CVS管理工具一样,SVN是一个跨平台的开源的版本控制系统.Svn版本管理工具管理着随时间改变的各种数据.这些数据放置 ...

  2. 管理多个gradle,SDKMAN

    背景:同一台机器上有两个app需要编译,但是两个app的gradle版本不一致,所以需要安装一个管理gradle版本的工具   sdkman:(Software Development Kit Man ...

  3. 微信小程序分享朋友圈

    原理:canvas生成图片再保存到手机 JS onShow: function () { var that = this; //1. 请求后端API生成小程序码 // that.getQr(); // ...

  4. python中的集合

    在python中,普通集合是可变数据类型 通过以下案例说明: >>> s = {1, 2, 3, 4} >>> id(s) 2108634636808 >&g ...

  5. sql转百分比并保留两位小数

    --转百分比并保留两位小数 select ProfitRate =Convert(nvarchar(20), (Convert(decimal(18,2),((DayPrice -MyPrice)*1 ...

  6. OO_多线程电梯_单元总结

    概述: 面向对象的第二单元是多线程电梯.第一次实现一部傻瓜电梯,每次只送一个人:第二次实现一部可稍带电梯:第三次实现三部可稍带电梯. 一.设计策略 1.第5.6次作业设计思路 第5.6次作业的架构相似 ...

  7. Java创建对象的初始化顺序

    1. 初始化块 初始化块通常写在类的构造方法之前,由花括号括起来,通常包含对成员属性进行初始化的语句: 初始化块分为instance初始化块和static初始化块,初始化块在构造方法执行之前被执行: ...

  8. 微信小程序开发过程问题总汇

    之前在开发一个控车小程序,把过程中稍微需要搜索的问题做了记录. 1. 关键词:本地资源图片无法通过WXSS获取 描述:做小程序开发的时候,如果你需要使用图片作为背景,也就是想使用background- ...

  9. Quartz.Net进阶之七:QuartzNet其他的功能简述

    一.介绍 今天是这个系列的最后一篇文章了,主要功能说的差不多了,我们来看看其他相关的内容.话说回来,虽然是这个系列的最后一篇文章,并不代表Quartz的东西就这么点,学习阶段,就这些了,如果以后有了使 ...

  10. H5基础

    <html> <head lang="en"> <meta charset="utf_8">                 ...