generator 原理

174 阅读3分钟

题目:

function* foo(num) {

  let i = num || 0;

  yield i ++;

  yield i ++

  yield i ++

}



const gen = foo(1);

gen.next() // {value: 1, done: false}

gen.next() // {value: 2, done: false}

gen.next() // {value: 3, done: true}

题目分析

  1. gen能调用next方法,且返回了value和done
  • 说明foo执行后返回了一个对象

  • 第一步:foo执行后返回的对象起码有next方法。

  • 第二部:每次执行next后,都需要执行对应yield语句的运算。

怎么执行到对应的yield语句?

答案:1:采用switch case语句,模拟一个状态机,每次执行case 语句后,将状态指向在一个case语句)

2:采用全局的context.nextStep作为下次执行的指引,每次执行完当前的yield后,将 context.nextStep指向到下一个case语句。


  • 第三步:每次执行yeild语句,怎么拿到上次的i,进行累加?

答案:用闭包来保存这个i,让每次调用next都能访问到i,进行++处理


  • 第四步:每次执行对应的yield语句,返回对应的value和done,并将context.nextStep指向下一个case

  • 第五步:到最后一次时,代表已经执行结束,不处理context.nextStep,返回的value和done都是undefined
function foo(num) {

    let i = num || 0;

    const context = {

        nextStep: 2,

    };

    // 第一步:初始执行后返回一个带有next方法的对象

    return {

        next: function () {

            // 这是一个必报,保证每次调用next都能访问到context和i

            function genWarp(context) {

                switch (context.nextStep) { // 根据context.nextStep状态执行对应的语句执行i ++

                    case 2:

                        context.nextStep = 4; // 第四步:每次执行对应的yield语句,返回对应的value和done,并将context.nextStep指向下一个case

                        return {

                            value: i++, // 第三步:采用闭包的形式让之后的调用都可以访问i,进行i++

                            done: false,

                        }

                    case 4:

                        context.nextStep = 6;

                        return {

                            value: i++,

                            done: false

                        }

                    case 6:

                        context.nextStep = 8;

                        return {

                            value: i++,

                            done: true

                        }

                    case 8: // 第五步:到最后一次时,代表已经执行结束,不处理context.nextStep,返回的value和done都是undefined

                        return undefined;

                }

            }

            // 第二部: 调用genWarp传入context,context.nextStep为下次需要执行的case值

            const { value, done } = genWarp(context) || {}

            // 第三部返回执行genWarp(context)后的value和done

            return {

                value,

                done,

            }

        }

    }

}





const gen = foo(1);

gen.next() // {value: 1, done: false}

gen.next() // {value: 2, done: false}

gen.next() // {value: 3, done: true}

思考下上面的这篇代码,如果遇到其中某个yield语句是异步的代码后。怎样让下一个gen.next()在上一个gen.next()执行完成后,再执行呢?

function foo(num) {

    let i = num || 0;

    const context = {

        nextStep: 2,

    };

    // 第一步:初始执行后返回一个带有next方法的对象

    return {

        next: function () {

            // 这是一个必报,保证每次调用next都能访问到context和i

            function genWarp(context) {

                while (1) {

                    switch (context.nextStep) { // 根据context.nextStep状态执行对应的语句执行i ++

                        case 2:

                            context.nextStep = 4; // 第四步:每次执行对应的yield语句,返回对应的value和done,并将context.nextStep指向下一个case

                            return {

                                value: i++, // 第三步:采用闭包的形式让之后的调用都可以访问i,进行i++

                                done: false,

                            }

                        case 4:

                            context.nextStep = 6;

                            return {

                                value: i++,

                                done: false

                            }

                        case 6:

                            context.nextStep = 8;

                            return {

                                value: i++,

                                done: true

                            }

                        case 8: // 第五步:到最后一次时,代表已经执行结束,不处理context.nextStep,返回的value和done都是undefined

                            return undefined;

                    }

                }



            }

            // 第二部: 调用genWarp传入context,context.nextStep为下次需要执行的case值

            const { value, done } = genWarp(context) || {}

            // 第三部返回执行genWarp(context)后的value和done

            return {

                value,

                done,

            }

        }

    }

}





const gen = foo(1);

gen.next() // {value: 1, done: false}

gen.next() // {value: 2, done: false}

gen.next() // {value: 3, done: true}
  • genWarp内加上while(1)循环,当异步函数执行完后再将context.nextStep指向下一个状态。(只要异步函数没有执行完,就一直在这个while(1)循环内)