ES6的迭代器和生成器浅析

158 阅读2分钟

迭代器

编写一个函数,该函数接收数组为参数,返回next()方法。该方法返回两个值:value和done。每次调用next()方法,value会返回上次调用next()方法后,数组被遍历的下一个值;done返回数组是否遍历完成。

function iteratorDecorator(){
    //前置条件判断略
    let currentIndex = 0;
    return {
        next: () => {
            let value;
            if(currentIndex < this.length){
                value = arr[currentIndex++];
            }
            return {
                value,
                done: currentIndex >= this.length
            }
        }
    }
}

测试数组:


const testArr = [1, 3, 5];
const iteratorArr = iteratorDecorator.call(testArr);

调用iteratorArr的next方法和done方法,结果如下:

result1 = iteratorArr.next(); // value1 = 1;
result1.value; // 1;
result1.done;   // false;
result2 = iteratorArr.next(); 
result2.value //3
result2.done;  // false;
result3 = iterator.next();
result3.value //5
result3.done; // true

至此完成了一个数组迭代器的创建。

生成器

上述迭代器首先需要调用下iteratorDecorator函数,才能迭代下去。如果将这个初始化交给一个函数做,那这个函数就是生成器。

function* iteratorGen(arr){
    for(let i=0; i< arr.length; i++){
        yield arr[i]
    }
}


const iterator  = iteratorGen([1,3,5]);
iterator.next() // {value: 1, done: false}
iterator.next() // {value: 3, done: false}
iterator.next() // {value: 5, done: false}

以上代码完成了一个生成器函数的创建。调用生成器函数,会返回一个迭代器。之后可以正常的使用这个迭代器来输出每一步的结果了。

这个生成器函数更加直观,对我们隐藏了next()方法的实现。只需要关注输入:生成器入参,以及输出: yield的值,即可。

包含异步代码的生成器

yield指令只能在function* 中生效。所以在promise的回调/setTimeout的回调中使用yield是会抛出错误的。

setTimeout之后返回yield:

function* iteratorGen(){
    yield (new Promise((resolve) => {
        setTimeout(() => {
            resolve(true)
        }, 5000);
    
    }));
    yield "end";
}

const iterator = iteratorGen();
iterator.next().value.then(res => {
    console.log(res);   //true

});
iterator.next().value; //end

以上代码中yield后面紧跟一个promise。 可以看到想要之行这个生成器函数,还需要在第一步返回的then回调中继续后续的流程。这是直接使用生成器函数的一个缺陷。

如何改进呢?我们写一个run函数,包装生成器。在调用run()函数后,可以给到我们想要的值。

function run(generatorFunc){
    //获取一个生成器的迭代对象
    const iterator = generatorFunc();
    //开始执行第一步
    let res = iterator.next();
    //循环执行,直到迭代对象的返回值done == true
    if(!res.done){
        res = iterator.next(res.value);
    }
    
   //此时的res就是最终结果
   console.log(res.value); 
}

此时我们实现了一个执行函数run,这个执行函数帮我们一步步的去完成迭代对象的next()方法,而不需要我们手动写代码执行。

写到这里,不难看出,async/await是不是就是一个包装了执行函数的生成器呢?