javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JS Generator

JavaScript Generator函数使用分析

作者:月光晒了很凉快

生成器Generator是JavaScript ES6引入的特性,它让我们可以分段执行一个函数。但是在谈论生成器(Generator)之前,我们要先了解迭代器Iterator

1. Generator的定义和执行

如果说 Promise 是为了解决回调地狱的难题出现的,那么 Generator 就是为了解决异步问题而出现的。

普通函数,如果调用它会立即执行完毕;Generator 函数,它可以暂停,不一定马上把函数体中的所有代码执行完毕,正是因为有这样的特性,它可以用来解决异步问题。

定义一个 Generator 函数,定义的方式和定义一个普通函数是类似的,不同之处在于它在 function 和函数名之间有一个*号。

Generator 函数返回是一个迭代器对象,需要通过 xx.next 方法来完成代码执行。在调用 generator 函数时,它只是进行实例化工作,它没有让函数体里面的代码执行,需要通过 next 方法来让它执行,比如像下面这样:

function* gen() {
    console.log(1)
}
// 定义迭代器对象
const iterator = gen()
iterator.next() // 如果不执行这一局代码,1不会被打印

当 next 方法执行时遇到了 yield 就会停止,直到你再次调用 next 方法。比如像下面这样:

function* gen() {
    yield 1
    console.log('A')
    yield 2
    console.log('B')
    yield 3
    console.log('C')
    return 4
}
// 定义迭代器对象
const iterator = gen()
iterator.next() // 执行 gen 函数,打印为空,遇到 yield 1 停止执行
iterator.next() // 继续执行函数,打印 A,遇到 yield 2 停止执行
iterator.next() // 继续执行函数,打印 B,遇到 yield 3 停止执行
iterator.next() // 继续执行函数,打印 C

next 方法调用时,它是有返回值的,它的返回值就是 yield 后面的值或函数的返回值。比如下面这个例子:

// 同步代码
function* gen() {
    yield 1
    console.log('A')
    yield 2
    console.log('B')
    yield 3
    console.log('C')
    return 4
}
// 定义迭代器对象
const iterator = gen()
// 异步代码
console.log(iterator.next()) // 打印为空  next返回 {value:1,done:false}
console.log(iterator.next()) // A  next返回 {value:2,done:false}
console.log(iterator.next()) // B  next返回 {value:3,done:false}
console.log(iterator.next()) // C  next返回 {value:4,done:true},如果函数有return值,最后一个next方法,它的value值为return的值 value:4;如果没有。值为 undefined

拓展:其实之所以我们说 Generator 能够把异步变同步,是因为 Generator 函数中我们只需要写同步代码就可以,真正执行异步操作的是迭代器对象。在复杂的业务逻辑中,大量使用迭代器对象来执行异步操作,会使得代码变得很不优雅,于是 ES7 中就推出了 async await 方案来实现异步变同步。在 async await 方案中可以只书写同步代码,真正的异步操作被封装在底层,这样的写法,使得代码变优雅了很多。

2. Generator中yield在赋值号左边的情况

yield 在等号右边时,它的返回值并不会返回给等号左边的变量,依然会返回给 next 方法。

function* gen(num) {
    let r1 = yield 1
    console.log('r1', r1);
    let r2 = yield 2
    console.log('r2', r2);
    let r3 = yield 3
    console.log('r3', r3);
}
const iterator = gen()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next()) 

这是因为 generator 函数在遇到 yield 时就已经暂停执行了,并不会执行到赋值操作,直到在执行完 next 方法之后,才会继续向下执行赋值操作。如果我们想要 r1/r2/r3 有值,我们可以用 next 方法进行传值。就像下面这样:

function* gen(num) {
    let r1 = yield 1
    console.log('r1', r1);
    let r2 = yield 2
    console.log('r2', r2);
    let r3 = yield 3
    console.log('r3', r3);
}
const iterator = gen()
iterator.next() // 第一个 next 方法不用给值,即使给值也不会生效
iterator.next('A')
iterator.next("B")
iterator.next('C')

3. Generator函数嵌套使用

function* gen1() {
    yield 1
    yield 2
}
function* gen2() {
    yield 3
    // generator函数的嵌套
    // 这种写法对应 方案1
    // yield gen1()
    yield* gen1()
    yield 4
}
const iterator = gen2()
console.log(iterator.next()); // {value:3,done:false}
// 如果我们想执行到 gen1 中的 yield 值
// console.log(iterator.next()); // {value:generator实例,done:false}
// let itor = iterator.next().value
// console.log(itor.next()); // {value:1,done:false}
// console.log(itor.next()); // {value:2,done:false}
// 方案2
console.log(iterator.next()); // {value:1,done:false}  你需要在yield后面加一个*,让它知道后面是一个generator对象
console.log(iterator.next()); // {value:2,done:false}
console.log(iterator.next()); // {value:4,done:false}
console.log(iterator.next()); // {value:undefined,done:true}

4. 使用generator函数完成网络请求

// 使用generator来完成异步网络请求,它还是要利用到promise
// 模拟网络请求
function request(num = 1) {
    return new Promise((resolve, reject) => {
        return setTimeout(() => {
            resolve(++num)
        }, 1000);
    })
}
// generator函数中的代码,发起的网络请求它就类似于同步写法
function* gen(num) {
    // yield右侧是一个promise对象
    let r1 = yield request(10)
    console.log('r1', r1);
    let r2 = yield request(r1)
    console.log('r2', r2);
    let r3 = yield request(r2)
    console.log('r3', r3);
}
const iterator = gen(10)
iterator.next().value.then(ret1 => {
    iterator.next(ret1).value.then(ret2 => {
        iterator.next(ret2).value.then(ret3 => {
            iterator.next(ret3)
        })
    })
})

上面的写法不够优雅,当有多个网络请求时,异步操作部分的代码会变得非常复杂,所以我们可以通过 co 库中的迭代函数来改写一下:

// 使用generator来完成异步网络请求,它还是要利用到promise
// 模拟网络请求
function request(num) {
    return new Promise((resolve, reject) => {
        return setTimeout(() => {
            resolve(++num)
        }, 1000);
    })
}
// generator函数中的代码,发起的网络请求它就类似于同步写法
function* gen(num) {
    // yield右侧是一个promise对象
    let r1 = yield request(10)
    console.log('r1', r1);
    let r2 = yield request(r1)
    console.log('r2', r2);
    let r3 = yield request(r2)
    console.log('r3', r3);
    let r4 = yield request(r3)
    console.log('r4', r4);
    let r5 = yield 'ok'
    console.log('r5', r5);
}
// 通过co库实现
function co(generator, ...params) {
    const iterator = gen(...params)
    // 迭代函数
    const next = n => {
        let { value, done } = iterator.next(n)
        // 判断一下value它是一个promise对象,如果不是promise对象则需要手动转为promise对象,或抛异常
        if (value != undefined && typeof value.then != "function") {
            throw new Error('必须为promise对象')
            // value = Promise.resolve(value)
        }
        if (done) return;
        // value.then(ret => next(ret))
        value.then(next)
    }
    next(0)
}
co(gen, 100)

到此这篇关于JavaScript Generator函数使用分析的文章就介绍到这了,更多相关JS Generator内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文