迭代器
编写一个函数,该函数接收数组为参数,返回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是不是就是一个包装了执行函数的生成器呢?