【JS】什么是生成器?

89 阅读3分钟

一、生成器的语法

// 定义一个生成器
function* generator(){
    yield 1
    yield 2
    yield 3
}

// 调用生成器创建一个迭代器
let iterator = generator()

// 使用这个 iterator 对象
iterator.next()  // {value: 1, done: false}
iterator.next()  // {value: 2, done: false}
iterator.next()  // {value: 3, done: false}
iterator.next()  // {value: undefined, done: true}

// 也可以直接使用 for...of 遍历
// 注意:不能和上面的一连串 next() 同时写,因为 next() 已经迭代完了,已经不能迭代出东西了
for(const val of iterator){
    console.log(val) // 1, 2, 3
}

如果在 next() 中传参会怎样? 会将这个参数赋值给上一个 yield。具体看下面的例子

// 还是先定义一个生成器函数,并接收一个初始参数
function* generator(x) {
    // 定义一个变量用于接收 yield 的值
    let res1 = yield x
    console.log(res1)  // 1
    let res2 = (yield res1) + 1
    console.log(res2)  // 2
    let res3 = yield res2 + 1
    console.log(res3)  // 1
}

let x = 1
let iterator = generator(x)

console.log(iterator.next())  // {value: 1, done: false}
console.log(iterator.next(x))  // {value: 1, done: false}
console.log(iterator.next(x))  // {value: 3, done: false}
console.log(iterator.next(x))  // {value: undefined, done: true}
在上面这个例子中,我们共调用了 4next()
第一次:返回的是参数 x 的值(即 1),此时还没给 res1 赋值
第二次:next() 传入 x,作为整个 (yield x) 的值(即 1),赋值给 res1(即 1),接着打印 res1,然后返回的是 (yield res1) 中 res1 的值(即 1),此时还没给 res2 赋值
第三次:next() 传入 x,作为整个 (yield res1) 的值(即 1),这个值 + 1 后赋值给 res2(即 2),接着打印 res2,然后返回的是 (yield res2 + 1) 中 (res2 + 1) 的值(即 3),此时还没给 res3 赋值
第四次:next() 传入 x,作为整个 (yield res2 + 1) 的值(即 1),赋值给 res3,接着打印 res3,无返回值,迭代完成

二、生成器的执行机制

第一步:通过调用生成器函数来创建迭代器(此时生成器的代码不会执行)
第二步:通过迭代器调用 next 执行生成器函数里面的代码
第三步:执行到 yield,暂时跳出函数,并把 yield 后面的值(加工成一个对象)返回到调用 next() 的地方

那么迭代和遍历有什么区别呢?

【过程】迭代强调的是依次取出,不能确定可以取出的值有多少,也不能保证去把数据全部取完
【结果】遍历必须保证数据的长度,循环不断地全部取出,针对于数据量过大的情况下使用遍历,需要时间过长

三、生成器的应用场景

模拟实现 async-await 的执行

// 应用场景:第一个异步请求成功后再去发送第二个请求,第二个请求成功后,再去发送第三个请求。(每一个请求是基于前面一次请求成功之后的结果,才来发送请求)
// 请求的函数,返回的是一个 promise 对象
function xxxxAPI(x) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(++x)
        }, 1000)
    })
}

// promise 嵌套调用的写法
// iter.next().value.then(res1 => {
//     iter.next(res1).value.then(res2 => {
//         iter.next(res2).value.then(res3 => {
//             iter.next(res3)
//         })
//     })
// })

// async-await 原版写法
// async function genFunc(x) {
//     let res1 = await xxxxAPI(x)
//     console.log(`第 1 次请求结果为:${res1}`)
//     let res2 = await xxxxAPI(res1)
//     console.log(`第 2 次请求结果为:${res2}`)
//     let res3 = await xxxxAPI(res2)
//     console.log(`第 3 次请求结果为:${res3}`)
// }



// asyncFunc 是我们封装的函数
function asyncFunc(genFn, ...params) {
    // 利用生成器函数(传参),创建一个迭代器
    let iter = genFn(...params)
    const next = x => {
        // 调用一次 next,返回一个对象,解构出 value(是一个 promise 对象)和 done(是否迭代完成)
        let {value, done} = iter.next(x)
        
        // 如果没有下一个值,就 return
        if(done) return
        
        // 如果有值
        value.then(next)
        // 上面这句代码需要单独理解一下
        // 首先需要明确的是:next 是一个函数,这个函数接收一个参数 x
        // 而这里的 value 恰好就是一个 promise 对象,后面接上 then,传入一个回调 next
        // 这样其实就是普通的 promise 对象 then 的写法
    }
    next()
}

// 模拟 async-await 执行过程写法
asyncFunc(function* genFunc(x) {
    let res1 = yield xxxxAPI(x)
    console.log(`第 1 次请求结果为:${res1}`)
    let res2 = yield xxxxAPI(res1)
    console.log(`第 2 次请求结果为:${res2}`)
    let res3 = yield xxxxAPI(res2)
    console.log(`第 3 次请求结果为:${res3}`)
}, 0)