今天在学习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. “AS3.0高级动画编程”学习:第一章高级碰撞检测

    AdvancED ActionScript 3.0 Animation 是Keith Peters大师继"Make Things Move"之后的又一力作,网上已经有中文翻译版本了 ...

  2. 从裸机到实时操作系统RTOS

    最近有点闲,公司新年过后一直没有项目,手头上维护的两个程序也比较稳定. 想起来去年做的商业时钟,做了一半,销售反馈回来说,市场不明朗,不建议往下开展,就搁置了,趁着现在有空,把他捡起来. 原来的代码都 ...

  3. windows内核驱动内存管理之Lookaside使用

    Windows内存管理中使用了类似于容器的东西,叫做Lookaside对象,每次程序员申请内存都会从Lookaside里面申请,只有不足的时候,Lookaside才会向内存又一次申请内存空间,这样减少 ...

  4. curl get方式

    提交数据到https时,需要pem证书来加密.我们使用浏览器访问https的时候,浏览器会自动加载网站的安全证书进行加密.但是你用curl请求https时,没有通过浏览器,就只有自己手动增加一个安全证 ...

  5. Linux命令面试集

    Linux:免费开源,多用户多任务,衍生出很多附属版本,例如常用的RedHat... 常用指令 ls        显示文件或目录 -l           列出文件详细信息l(list) -a   ...

  6. 词根:sol = sun(太阳) 词根:sol = alone/single, whole/entire (单独的)

    词根:sol = sun(太阳) sol 这个词根有的书上如刘毅字典,刘洪波英文字根词源精讲等上面说来自拉丁语的sol(=sun),有的书如赢在单词上面说sol 来自拉丁语的solari,但不管哪种说 ...

  7. jsp4

    SESSION ID=B564A2D318ECDF70EB94C6DE2080E951 SESSION ID=B564A2D318ECDF70EB94C6DE2080E951 SESSION ID=B ...

  8. 项目总结22:Java UDP Socket数据的发送和接收

    项目总结22:Java UDP Socket数据的发送和接收 1-先上demo 客户端(发送数据) package com.hs.pretest.udp; import java.io.IOExcep ...

  9. Find out where to contain the smartforms

    Go to table E071 and give smarforms name and it will give the transport req for that. Run SE03, choo ...

  10. ROS零门槛学渣教程系列(一)——ubuntu安装

    本教程使用虚拟机安装ubuntu 实验前准备:下载ubuntu系统镜像 本教程使用的是ubuntu14.04lts版本,有能力的读者可自行下载安装. 推荐使用本人制作的镜像,该镜像已安装好ROS.和配 ...