Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同
特征:
- function 命令与函数名之间有一个星号
- 函数体内部使用 yield 语句定义不同的内部状态
function * f () {
yield 'hello'
yield 'world'
return '!'
}
let fg = f()
// 调用遍历器对象的 next 方法,使得指针移向下一个状态,直到遇到下一条 yield 语句(或 return 语句)为止
// done 为 true 时遍历结束
console.log(fg.next())// {value: "hello", done: false}
console.log(fg.next())// {value: "world", done: false}
console.log(fg.next())// {value: "!", done: true}
console.log(fg.next())// {value: undefined, done: true}
yield 表达式
Generator 函数内部提供了一种可以暂停执行的函数,yield 语句就是暂停标志
遍历器对象的 next 方法的运行逻辑如下:
- 遇到 yield 语句就是暂停执行后面的操作,并将紧跟在 yield 后的表达式的值作为返回的对象的 value 值
- 下次调用 next 方法继续向下执行后面的 yield 语句
- 直到 return 为止,将 return 的值赋值给 value,若无 return 后面的值 value 都为 undefined,此时 done 值为 true,for of 遍历停止
注意:yield 表达式只能用在 Generator 函数里面,用在其他地方都会报错
// yield 表达式如果用在另一个表达式之中,必须放在圆括号里
function * f () {
console.log('hello' + yield)// error
console.log('hello' + yield 123)// error
console.log('hello' + (yield))// ok
console.log('hello' + (yield 123))// ok
}
// yield 表达式用作函数参数或放在赋值表达式右边,可以不加括号
function * f () {
foo(yield 'a', yield 'b')// ok
let input = yield// ok
}
Generator 内部调用 Generator:
注意:任何数据只要具有 Iterator 接口,就可以被 yield*
遍历
function * f1 () {
yield 'c'
yield 'd'
}
function * f2 () {
yield 'a'
yield 'b'
// yield * ['c', 'd'] 会达到一样的效果
yield * f1()
yield 'e'
}
for (let i of f2()) {
console.log(i)// a b c d e
}
next 传参
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
重点:
next方法的参数表示上一个yield表达式的返回值!!不是函数中参数(如本例的x)的值!不可混淆!!!
代码第一次调用b的next方法时,x=5,返回x+1的值6
第二次调用next方法,将上一次yield表达式的值设为12,即yield(x+1)==> 12
,因此y等于24,z=y / 3为8
第三次调用next方法,将上一次yield表达式的值设为13,即yield(y/3) ==> 13
,因此z等于13,这时x等于5,y等于24,所以return语句的值等于42
调用 Generator 函数返回一个遍历器对象
function * f () {
yield 'hello'
yield 'world'
yield '!'
}
let fg = f()
for (let i of fg) {
console.log(i)// hello world !
}
// 任何一个对象的 Symbol.iterator 方法等于该对象的遍历器对象生成函数,调用该函数会返回该对象的一个遍历器对象
// 由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的 Symbol.iterator 属性,从而使得该对象具有 Iterator 接口
let fg2 = {}
fg2[Symbol.iterator] = function * () {
yield 'hello'
yield 'world'
yield '!'
}
console.log([...fg2])// (3) ["hello", "world", "!"]
实例
- generator 异步请求
// 嵌套 axios
this.$http.get('/data1', data1, (res, err) => {
if (err) {
return handle(err)
}
this.$http.get('/data2', data2, (res, err) => {
if (err) {
return handle(err)
}
return success(res)
})
})
// generator
try {
let r1 = yield this.$http.post('/data1', data1)
let r2 = yield this.$http.post('/data2', data2)
success(r2)
} catch (e) {
handle(e)
}
- 异步读取文件
// Thunk 方法
const fs = require('fs')
// Thunk 函数 :用于 Generator 函数的自动流程管理
// 推荐使用 thunkify 模块 ,地址 https://github.com/tj/node-thunkify
const Thunk = function (fn) {
return function () {
let args = Array.prototype.slice.call(arguments)
return function (callback) {
args.push(callback)
return fn.apply(this, args)
}
}
}
let readFile = Thunk(fs.readFile)
// 基于 Thunk 函数的 Generator 执行器
// 推荐使用 co 模块 ,地址 https://github.com/tj/node-thunkify
function run (fn) {
let gen = fn()
function next (err, data) {
let result = gen.next(data)
if (result.done) return
result.value(next)
}
next()
}
// Generator 执行异步文件读取
function * g () {
try {
let data1 = yield readFile('./data1.json')
let data2 = yield readFile('./data2.json')
let data3 = yield readFile('./data3.json')
console.log(data1.toString(), data2.toString(), data3.toString())
} catch (e) {
console.log(e)
}
}
// 执行 Generator 函数
// 注意:使用执行器或 co 的前提是 yield 后面只能是 Thunk 函数或 Promise 对象
run(g)
// Promise 方法
const fs = require('fs')
// Promise 函数 :用于 Generator 函数的自动流程管理
// 推荐使用 thunkify 模块 ,地址 https://github.com/tj/node-thunkify
function promise (fn) {
return new Promise(((resolve, reject) => {
fs.readFile(fn, (err, data) => {
if (err) return reject(err)
resolve(data)
})
}))
}
const readFile = promise
// 基于 Thunk 函数的 Generator 执行器
// 推荐使用 co 模块 ,地址 https://github.com/tj/node-thunkify
function run (fn) {
let gen = fn()
function next (data) {
let result = gen.next(data)
if (result.done) return result.value
result.value.then((data) => {
next(data)
})
}
next()
}
// Generator 执行异步文件读取
function * g () {
try {
let data1 = yield readFile('./data1.json')
let data2 = yield readFile('./data2.json')
let data3 = yield readFile('./data3.json')
console.log(data1.toString(), data2.toString(), data3.toString())
} catch (e) {
console.log(e)
}
}
// 执行 Generator 函数
// 注意:使用执行器或 co 的前提是 yield 后面只能是 Thunk 函数或 Promise 对象
run(g)
- 执行 Generator 并取值
function * fun () {
yield 1
yield 2
yield 3
yield 4
yield 5
}
function run (fn, resolve) {
let gen = fn()
function next (arr = []) {
let result = gen.next()
if (result.done) return resolve(arr)
arr.push(result.value)
next(arr)
}
next()
}
run(fun, data => {
console.log(data)// (5) [1, 2, 3, 4, 5]
})
- 斐波那契数列
function * f () {
let [oldVal, newVal] = [0, 1]
for (; ;) {
[oldVal, newVal] = [newVal, oldVal + newVal]
yield newVal
}
}
for (let i of f()) {
if (i > 1000) break
console.log(i)
}
- for of 遍历对象封装
function * f1 (obj) {
let keys = Reflect.ownKeys(obj)
for (let key of keys) {
yield [key, obj[key]]
}
}
let obj = { a: '1', b: '2', c: '3' }
for (let [key, value] of f1(obj)) {
console.log(key, value)
}
// 另一种写法:将 Generator 函数添加到对象的 Symbol.iterator 属性上
function * f1 () {
let keys = Object.keys(this)
for (let key of keys) {
yield [key, this[key]]
}
}
let obj = { a: '1', b: '2', c: '3' }
obj[Symbol.iterator] = f1
for (let [key, value] of obj) {
console.log(key, value)
}
- 其他方法调用 Generator 接口
function * f () {
yield 1
yield 2
yield 3
}
// 扩展运算符
console.log([...f()])// (3) [1, 2, 3]
// Array.from 方法
console.log(Array.from(f()))// (3) [1, 2, 3]
// 解构赋值
let [x, y, z] = f()
console.log(x, y, z)// 1 2 3
- 遍历嵌套数组的所有成员
function * iterTree (tree) {
if (Array.isArray(tree)) {
for (let i = 0; i < tree.length; i++) {
yield * iterTree(tree[i])
}
} else {
yield tree
}
}
const tree = ['a', ['b', 'c'], ['d', 'e', ['f', 'g']]]
for (let x of iterTree(tree)) {
console.log(x)// a b c d e f g
}
- 遍历完全二叉树
// 二叉树构造函数,三个参数分别为左树、当前节点、右树
function Tree (left, label, right) {
this.left = left
this.label = label
this.right = right
}
// 下面是中序遍历函数
// 由于返回的是一个遍历器,所以要用 generator 函数
// 函数体内采用递归算法,所以左树和右树要用 yield* 遍历
function * inorder (t) {
if (t) {
yield * inorder(t.left)
yield t.label
yield * inorder(t.right)
}
}
// 生成二叉树
function make (arr) {
// 判断是否为叶节点
if (arr.length === 1) return new Tree(null, arr[0], null)
return new Tree(make(arr[0]), arr[1], make(arr[2]))
}
let tree = make([[['a'], 'b', ['c']], 'd', [['e'], 'f', ['g']]])
// 遍历二叉树
let result = []
for (let node of inorder(tree)) {
result.push(node)
}
console.log(result)// (7) ["a", "b", "c", "d", "e", "f", "g"]
- 状态机
let clock = function * () {
while (true) {
console.log('0')
yield
console.log('1')
yield
}
}
let status=clock()
status.next()// 0
status.next()// 1
status.next()// 0
status.next()// 1