generator -> 生产者 yield -> 产出javascript
generator 函数是 ES6 提供的一种异步编程解决方案vue
执行 generator 函数返回的是一个遍历器对象
, 也就是说, 咱们可使用 next
方法, 来遍历 generator 函数内部的每个状态java
既然 generator 函数内部具备多个状态, 那么总该有一个标识来决定函数在遍历过程当中应该在哪里停下来, 因此咱们须要 yield
编程
下面经过一个简单实例, 详细解释 yield 的工做原理json
function* foo() {
yield 'hello'
console.log('come from second yield')
yield 'world'
return 'ending'
}
const g = foo()
// 执行过程
> g.next()
< { value: 'hello', done: false }
> g.next()
log: come from second yield
< { value: 'world', done: false }
> g.next()
< { value: 'ending', done: true }
> g.next()
< { value: undefined, done: true }
复制代码
foo
函数会返回一个遍历器对象
, 调用遍历器对象的 next
方法, 使指针移向下一个状态next
方法, 内部指针就从函数头部上一次停下来的地方开始执行, 直到遇到下一条 yield 语句或 return 为止next
方法的返回对象:
next
方法, 都只会返回 { value: undefined, done: true }
从 generator 的行为能够看出, 实际上等同于 javascript 提供了一个手动 "惰性求值" 的语法功能api
注意事项:数组
console.log('hello' + (yiled 123))
generator 函数是能够传递参数的, 但 generator 函数有两种传参的方式bash
generator 函数传参跟普通函数传参是同样的, 一个参数能在 generator 函数的任何状态中读取到数据结构
function* foo(x) {
console.log(x)
yield 'step 1'
console.log(x)
yield 'step 2'
console.log(x)
return 'step 3'
}
const g = foo('hello world')
复制代码
以上代码片断, 不管在函数体的哪个状态, 都能读取到参数 hello world
异步
next 方法传递参数, 就跟普通函数传参彻底不同了
yield 语句自己没有返回值, 或者说老是返回 undefined, next 方法能够带一个参数, 该参数会被当作该状态以前全部 yield 语句的返回值
咱们先来看看下面的表达式
function* foo() {
const x = yield 10
console.log(x)
return 'ending'
}
const g = foo()
复制代码
g.next() log: undefined < { value: 'ending', done: true }
<br>
若是咱们但愿打印出来的 `x` 的值是 `hello world`, 就必须使用 next 方法传递参数
该参数会被当作上一条 yield 语句的返回值
``` javascript
> g.next()
< { value: 10, done: false }
> g.next('hello world')
log: hello world
< { value: 'ending', done: true }
复制代码
function* foo(x) {
let y = 2 * (yield (x + 5))
let z = yield y / 4 + 3
return (x + y - z)
}
const g = foo(10)
复制代码
g.next() // 1
g.next(4) // 2
g.next(8) // 3
复制代码
运算过程:
1.
x = 10
yield (10 + 5) => 15
> { value: 15, done: false }
2.
y = 2 * 4 => 8
yield (8 / 4 + 3) => 5
> {value: 5, done: false}
3.
x = 10
y = 8 // 保留了上一次 next 方法执行后的值
z = 8
return (10 + 8 - 8) => 10
> { value: 10, done: true }
复制代码
done = true
function* foo() {
try {
yield console.log(variate)
yield console.log('hello world')
} catch(err) {
console.log(err)
}
}
const g = foo()
g.next()
> ReferenceError: variate is not defined
at foo (index.js:3)
at foo.next (<anonymous>)
at <anonymous>:1:3
> { value: undefined, done: true }
复制代码
若是在内部 catch
片断中将错误使用全局方法 throw
抛出, 该错误依然可以被外部 try...catch
所捕获:
function* foo() {
try {
yield console.log(variate)
yield console.log('hello world')
} catch(err) {
throw err
}
}
const g = foo()
try {
g.next()
} catch(err) {
console.log('外部捕获', err)
}
> 外部捕获 ReferenceError: variate is not defined
at foo (index.js:3)
at foo.next (<anonymous>) at index.js:13 复制代码
由于 generator 函数执行后返回的是一个遍历器对象, 因此咱们可使用 for...of
循环来遍历它
function* foo() {
yield 1
yield 2
yield 3
return 'ending'
}
const g = foo()
for (let v of g) {
console.log(v)
}
// < 1
// < 2
// < 3
复制代码
for...of
循环, 不须要使用 next
语句done
属性为 true
, for...of
循环就会终止for...of
循环终止后不会返回 对象属性 done
为 true
的值, 因此上面的例子没有返回 return 里的 ending 值该语句用于在 generator 函数内部调用另外一个 generator 函数
function* bar() {
yield 3
yield 4
}
function* foo() {
yield 1
yield 2
yield* bar()
yield 5
return 'ending'
}
for (let v of foo()) {
console.log(v)
}
// < 1 2 3 4 5
复制代码
for...of
的语法糖for...of
替代yield* bar()
# 彻底等价于:
for (let v of bar()) {
yield v
}
复制代码
function* gen() {
yield* [1, 2, 3]
yield* 'abc'
}
for (let v of gen()) {
console.log(v)
}
// < 1 2 3 a b c
复制代码
Iterator
接口的数据结构, 都可以被 yield*
遍历和 yield
不一样的是(yield 自己没有返回值, 必须由 next 方法赋予), 若是当被 yield*
代理的 generator 函数有 return 语句时, return 返回的值能够被永久保存
function* foo() {
yield 2
return 'hello yield*'
}
function* gen() {
const a = yield 1
console.log(a) // -> undefined
const b = yield* foo()
console.log(b) // -> hello yield*
yield 2
console.log(b) // -> hello yield*
yield 3
}
const g = gen()
复制代码
const tree = [1, 2, [3, 4, [5, 6, [7, 8, 9]]], 10, 11]
function* loopTree(tree) {
if (Array.isArray(tree)) {
for (let i = 0; i < tree.length; i ++) {
yield* loopTree(tree[i])
}
} else {
yield tree
}
}
for (let v of loopTree(tree)) {
console.log(v)
}
复制代码
loopTree
, 该函数接收一个数组, 或是数字做为参数yield*
调用自身/** * 普通 xhr 请求封装 * @param {String} url 请求地址 * @return {void} void */
function call(url) {
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
const res = JSON.parse(xhr.responseText)
// 3. 请求成功, 将请求结果赋值给 result 变量, 并进入下一个状态
g.next(res)
} else {
console.log(`error: ${xhr.status}`)
}
}
}
xhr.open('get', url, true)
xhr.send(null)
}
function* fetchData() {
// 2. 发送 xhr 请求
const result = yield call('https://www.vue-js.com/api/v1/topics')
// 4. 打印出请求结果
console.log(result)
}
const g = fetchData()
// 1. 开始遍历 generator 函数
g.next()
复制代码
const obj = {
name: '木子七',
age: '25',
city: '重庆'
}
/** * 部署 Iterator 接口 * @param {Object} obj 对象 * @yield {Array} 将对象属性转换为数组 */
function* iterEntires(obj) {
let keys = Object.keys(obj)
for (let i = 0; i < keys.length; i ++) {
let key = keys[i]
yield [key, obj[key]]
}
}
for (let [key, value] of iterEntires(obj)) {
console.log(key, value)
}
< name 木子七
< age 25
< city 重庆
复制代码
generator 与 Promise 结合后, 实际上就是 async/await
的封装实现, 下面小结会详细描述 async/await
原理
// 1. 定义 generator 函数
// - 其返回的原生 fetch 方法是个 Promise 对象
function* fetchTopics() {
yield fetch('https://www.vue-js.com/api/v1/topics')
}
const g = fetchTopics()
// 2. 调用 next 方法
const result = g.next()
// 3. g.next() 返回的 { value: ..., done } 中的 value 就是 Promise 对象
result.value
.then(res => res.json())
.then(res => console.log(res))
复制代码
封装的方法实现
/** * 封装用来执行 generator 函数的方法 * @param {Func} generator generator 函数 */
function fork(generator) {
// 1. 传入 generator 函数, 并执行返回一个遍历器对象
const it = generator()
/** * 3. 遍历 generator 函数中的全部 Promise 状态 * go 函数会不停的使用 next 方法调用自身, 直到全部状态遍历完成 * @param {Object} result 执行 next 方法后返回的数据 */
function go(result) {
if (result.done) return result.value
return result.value.then(
value => go(it.next(value)),
error => go(it.throw(error))
)
}
// 2. 初次执行 next 语句, 进入 go 函数逻辑
go(it.next())
}
/** * 普通的 Promise 请求方法 * @param {String} url 请求路径 */
function call(url) {
return new Promise(resolve => {
fetch(url)
.then(res => res.json())
.then(res => resolve(res))
})
}
/** * 业务逻辑 generator 函数 * - 先请求 topics 获取全部主题列表 * - 再经过 topics 返回的 id, 请求第一个主题的详情 */
const fetchTopics = function* () {
try {
const topic = yield call('https://www.vue-js.com/api/v1/topics')
const id = topic.data[0].id
const detail = yield call(`https://www.vue-js.com/api/v1/topic/${id}`)
console.log(topic, detail)
} catch(error) {
console.log(error)
}
}
fork(fetchTopics)
复制代码
async 函数属于 ES7 的语法, 须要
Babel
或regenerator
转码后才能使用
async 函数就是 Generator 函数的语法糖, 其特色有:
Generator
函数的执行必须依靠执行器, 而 async
函数自带执行器, 也就是说, async
函数的执行与普通函数同样, 只要一行next
方法, 自动执行async
表示函数里有异步操做, await
表示紧跟在后面的表达式须要等待结果await
命令后面必须是 Promise
对象, 若是是其余原始类型的值, 其等同于同步操做function timeout() {
return new Promise(resolve => {
setTimeout(resolve, 2000)
})
}
async function go() {
await timeout().then(() => console.log(1))
console.log(2)
}
go()
// 执行输出, 先输出1 后输出2
// -> 1
// -> 2
复制代码
function timeout() {
return new Promise(resolve => {
setTimeout(resolve, 2000)
})
}
async function go() {
await timeout().then(() => console.log(1))
console.log(2)
}
go().then(() => console.log(3))
// -> 1
// -> 2
// -> 3
复制代码
下面咱们使用 async 函数来实现以前的异步请求例子
const call = url => (
new Promise(resolve => {
fetch(url)
.then(res => res.json())
.then(res => resolve(res))
})
)
const fetchTopics = async function() {
const topic = await call('https://www.vue-js.com/api/v1/topics')
const id = topic.data[0].id
const detail = await call(`https://www.vue-js.com/api/v1/topic/${id}`)
console.log(topic, detail)
}
fetchTopics().then(() => console.log('request success!'))
复制代码
总结: async 函数的实现比 Generator 函数简洁了许多, 几乎没有语义不相关的代码, 它将 Generator 写法中的自动执行器改在了语言层面提供, 不暴露给用户, 所以减小了许多代码量