ES6 generators in depth 一(译)
今天在学习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的方法
- 通过generator函数声明
function* genFunc() {}
- 通过generator函数表达式声明
const genFunc = function* (){}
- 将generator函数定义在对象字面量中
const obj = {
* genFunc() {}
}
4 作为一个成员函数(类函数)定义在class中
class MyClass{
* genFunc() {}
}
2.2 generator所扮演的角色
generator可以扮演以下角色:
迭代器(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和展开操作符...来直接处理这个序列。Observer(观察者 - 数据消费者):yield也可以获取next()传进来的参数,这意味着generator又变成了一个数据消费者。
协程(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实现了Iterable
和Iterator
接口,再次说明了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 一(译)的更多相关文章
- ES6 Generators并发
ES6 Generators系列: ES6 Generators基本概念 深入研究ES6 Generators ES6 Generators的异步应用 ES6 Generators并发 如果你已经读过 ...
- ES6 Generators的异步应用
ES6 Generators系列: ES6 Generators基本概念 深入研究ES6 Generators ES6 Generators的异步应用 ES6 Generators并发 通过前面两篇文 ...
- 深入研究ES6 Generators
ES6 Generators系列: ES6 Generators基本概念 深入研究ES6 Generators ES6 Generators的异步应用 ES6 Generators并发 如果你还不知道 ...
- ES6 Generators基本概念
ES6 Generators系列: ES6 Generators基本概念 深入研究ES6 Generators ES6 Generators的异步应用 ES6 Generators并发 在JavaSc ...
- Iterators & Generators in depth
Iterators & Generators in depth https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/It ...
- [ES6] Generators
Example 1: function *topicList(){ yield "ES2015"; yield "Semi-colons: good or bad?&qu ...
- [转] Understanding JavaScript’s async await
PS:Promise的用处是异步调用,这个对象使用的时候,call then函数,传一个处理函数进去,处理异步调用后的结果 Promise<Action>这样的对象呢,异步调用后的结果是一 ...
- ES6深度解析3:Generators
介绍ES6 Generators 什么是Generators(生成器函数)?让我们先来看看一个例子. function* quips(name) { yield "hello " ...
- 用简单的方法学习ES6
ES6 简要概览 这里是ES6 简要概览.本文大量参考了ES6特性代码仓库,请允许我感谢其作者@Luke Hoban的卓越贡献,也感谢@Axel Rauschmayer所作的[优秀书籍]//explo ...
随机推荐
- “AS3.0高级动画编程”学习:第一章高级碰撞检测
AdvancED ActionScript 3.0 Animation 是Keith Peters大师继"Make Things Move"之后的又一力作,网上已经有中文翻译版本了 ...
- 从裸机到实时操作系统RTOS
最近有点闲,公司新年过后一直没有项目,手头上维护的两个程序也比较稳定. 想起来去年做的商业时钟,做了一半,销售反馈回来说,市场不明朗,不建议往下开展,就搁置了,趁着现在有空,把他捡起来. 原来的代码都 ...
- windows内核驱动内存管理之Lookaside使用
Windows内存管理中使用了类似于容器的东西,叫做Lookaside对象,每次程序员申请内存都会从Lookaside里面申请,只有不足的时候,Lookaside才会向内存又一次申请内存空间,这样减少 ...
- curl get方式
提交数据到https时,需要pem证书来加密.我们使用浏览器访问https的时候,浏览器会自动加载网站的安全证书进行加密.但是你用curl请求https时,没有通过浏览器,就只有自己手动增加一个安全证 ...
- Linux命令面试集
Linux:免费开源,多用户多任务,衍生出很多附属版本,例如常用的RedHat... 常用指令 ls 显示文件或目录 -l 列出文件详细信息l(list) -a ...
- 词根:sol = sun(太阳) 词根:sol = alone/single, whole/entire (单独的)
词根:sol = sun(太阳) sol 这个词根有的书上如刘毅字典,刘洪波英文字根词源精讲等上面说来自拉丁语的sol(=sun),有的书如赢在单词上面说sol 来自拉丁语的solari,但不管哪种说 ...
- jsp4
SESSION ID=B564A2D318ECDF70EB94C6DE2080E951 SESSION ID=B564A2D318ECDF70EB94C6DE2080E951 SESSION ID=B ...
- 项目总结22:Java UDP Socket数据的发送和接收
项目总结22:Java UDP Socket数据的发送和接收 1-先上demo 客户端(发送数据) package com.hs.pretest.udp; import java.io.IOExcep ...
- 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 ...
- ROS零门槛学渣教程系列(一)——ubuntu安装
本教程使用虚拟机安装ubuntu 实验前准备:下载ubuntu系统镜像 本教程使用的是ubuntu14.04lts版本,有能力的读者可自行下载安装. 推荐使用本人制作的镜像,该镜像已安装好ROS.和配 ...