Generator 使用规律
从一道题目开始:
function* gen() {
const a = yield 1
console.log(a)
}
为了让其能成功打印出 1, 设计如下函数:
function step(gen) {
const it = gen()
let result
return function() {
result = it.next(result).value
}
}
进行如下调用:
var a = step(gen)
a()
a() // 1
从这个题目总结出规律:
next
的调用数比yield
的调用数多 1;- 第一个
next
传参无效, 从第二个next
开始传参有效并会作为yield
的结果返回;
生成器中的yield/next
除了控制能力外还有双向的消息通知能力: yield
后面跟的值能通过it.next().value
取到it.next()
括号中的值又能作为yield
的结果返回yield 暂停的位置
function* foo(url) {
try {
const value = yield request(url)
console.log(value)
} catch (err) {
...
}
}
const it = foo('http://some.url.1')
yield
后面跟着的语句执行完再进入暂停状态的, 在如上代码中, 当执行it.next()
时, 可以稍加转换为如下形式:function* foo(url) {
try {
const promise = request(url) // 当执行 it.next() 时, 这里是被执行的
const value = yield promise // 这里被暂停
console.log(value)
} catch (err) {
...
}
}
遇到 return/throw
- 遇到
return
总结: 遇到function* gen() {
yield 1
return 2
console.log('是否执行')
}
const it = gen()
it.next() // {value: 1, done: false}
it.next() // {value: 2, done: true}
it.next() // {value: undefined, done: true}
return
,generator
函数结束中断,done
变为true
; - 遇到
iterator
的throw
总结: 遇到function* gen() {
yield 1
console.log('是否执行')
}
var it = gen()
it.throw(new Error('boom')) // Error: boom
it.next() // {value: undefined, done: true}
iterator
的throw
,generator
函数运行中断,done
变为true
;Generator 的简单实现
Generator
是一个返回迭代器的函数, 下面是其简版实现:function foo(url) {
var state
var val
function process(v) {
switch (state) {
case 1:
console.log('requesting:', url)
return request(url)
case 2:
val = v
console.log(val)
return
case 3:
var err = val
console.log('Oops:', err)
return false
}
}
return {
next: function(v) {
if (!state) {
state = 1
return {
done: false,
value: process()
}
} else if (state === 1) {
state = 2
return {
done: true,
value: process(v)
}
} else {
return {
done: true,
value: undefined
}
}
},
throw: function() {
if (state === 1) {
state = 3
return {
done: true,
value: process(e)
}
} else {
throw e
}
}
}}
var it = foo('http://some.url.1')
Generator 函数的异步应用
以co
库来说, 现在已经统一为Generator + Promise
的调用方式, 下面进行简单的模拟:co(function* () {
const result = yield Promise.resolve(true)
console.log(result) // true
})
观察// 简版 promise
function co(gen) {
const it = gen()
const step = function(data) {
const result = it.next(data)
if (result.done) {
return result.value
}
result.value.then((data) => {
step(data)
})
}
step()
}
co
库发现,co
函数后返回的是promise
, 使用如下:
我们再对其稍加改造, 使之更加添近co(function* () {
const result = yield Promise.resolve(true)
return result // 这里有个语法, it.next() 碰到 return 后, 其值会变为 { value: result, done: true } 的形式
}).then((data) => {
console.log(data) // true
})
co
库:
function co(gen) {
return new Promise((resolve, reject) => {
const it = gen()
let result
const step = function(fn) {
try {
result = fn()
} catch(e) {
return reject(e)
}
if (result.done) { return resolve(result.value) }
result.value.then((data) => {
step(() => it.next(data))
}, (err) => {
step(() => it.throw(err)) // 这里为了让抛错直接在 generator 消化, 所以 step 内改传函数
})
}
step(() => it.next())
})
}