灏天阁

迭代器和生成器

· Yin灏

迭代器

内置迭代器

在字符串、数组、类型化数组、映射和集合中都已经内置了迭代器,通过 Symbol.iterator 方法就可以访问。

对象里面没有迭代器。

let str = 'hello';
console.log(str[Symbol.iterator]); // ƒ [Symbol.iterator]() { [native code] }
let iterator = str[Symbol.iterator]();
console.log(iterator.next()); // {value: 'h', done: false}

next()

在获得迭代器之后,就可以通过 next() 方法来返回序列中的对象,每一个对象包含 value 和 done 两个属性,其中,value 属性返回当前的值,而 done 属性则用来表示遍历是否已经结束,如果值为 false,就表示结束,反之,则表示已结束。

let str = 'hello';

let iterator = str[Symbol.iterator]();

console.log(iterator.next()); // {value: 'h', done: false}
console.log(iterator.next()); // {value: 'e', done: false}
console.log(iterator.next()); // {value: 'l', done: false}
console.log(iterator.next()); // {value: 'l', done: false}
console.log(iterator.next()); // {value: 'o', done: false}
console.log(iterator.next()); // {value: undefined, done: true}

对象与迭代器

对象是没有内置迭代器的,要想将对象转换为可迭代的对象,需要自行编写迭代器,又或者使用 Object 的 entries、keys 或 values 方法先提取对象的键值、键或值,再调用 Symbol.iterator 方法,例如,要在对象内添加遍历属性的迭代器,可以为对象添加 Symbol.iterator 方法。

class Car {
    constructor(color, passengers) {
        this.color = color;
        this.passengers = passengers;
    };
    [Symbol.iterator]() {
        return Object.keys(this)[Symbol.iterator]();
    }
}

let car = new Car('blue', 5);
let iterator = car[Symbol.iterator]();

console.log(iterator.next()); // {value: 'color', done: false}
console.log(iterator.next()); // {value: 'passengers', done: false}
console.log(iterator.next()); // {value: undefined, done: true}

异步迭代器

由于同步迭代器无法处理异步数据源,因此 ES2018 中引入了异步迭代器,以便处理异步数据,与同步迭代器剧透 Symbol.iterator 方法类似,异步迭代器也有自己的迭代方法 Symbol.asyncIterator。在调用 next 方法之后,异步迭代器会返回带有迭代结果的 Promise 对象。

生成器

要自定义一个迭代器,最大的麻烦是需要自己维护迭代器的状态,如当前索引。为了简化迭代器的编写,于是就有了生成器,它允许定义一个包含自有迭代算法的函数。同时可自行维护自己的状态。

基本语法

要指定函数生成器,需要在 function 关键字后加以 * ,并使用 yield 关键字来返回结果。

// 迭代器
function* generator() {
    yield 1;
    yield 2;
    yield 3;
}

let gen = generator();

console.log(gen.next()); // {value: 1, done: false}
console.log(gen.next()); // {value: 2, done: false}
console.log(gen.next()); // {value: 3, done: false}
console.log(gen.next()); // {value: undefined, done: true}

返回可迭代对象

当生成器要返回的是数组、字符串等可迭代的对象时,可以使用 yield * 语句来简化书写

function* generator() {
  yield* [1, 2, 3];
}

let gen = generator();

console.log(gen.next()); // {value: 1, done: false}
console.log(gen.next()); // {value: 2, done: false}
console.log(gen.next()); // {value: 3, done: false}
console.log(gen.next()); // {value: undefined, done: true}

在类或对象中定义生成器

要在类或对象中定义生成器,可以使用基本语法,也可以使用简写,在方法名前面加星号就可以了。

class Car {
  * generator() {
    yield 1;
    yield 2;
    yield 3;
  }
}

let car = new Car();
let gen = car.generator();
console.log(gen.next()); // {value: 1, done: false}

// 常规对象中使用生成器
let obj = {
  * generator() {
    yield 1;
    yield 2;
    yield 3;
  }
}

let gen1 = obj.generator();
console.log(gen1.next());

高级生成器

在调用 next() 方法时,可以通过传递参数给 next 的方式修改生成器内部状态的值。

let getId = function *(prefix, start) {
  while(true) {
    let stop = yield prefix + start++;
    if(stop) break;
  }
}

let gen = getId('div', 1);
console.log(gen.next()); // {value: 'div1', done: false}
console.log(gen.next()); // {value: 'div2', done: false}
console.log(gen.next()); // {value: 'div3', done: false}
console.log(gen.next(true));

如果不将 true 传递给 next 方法生成器会一直运作下去,而将 true 传递过去后,生成器的最后一个结果就会变成 true,并赋值给 stop,从而停止生成器的运作。

function* generator() {
    let index = 0;
    while (true) {
        let step = yield index++;
        console.log(step);
        if (step === 4) index = 0;
    }
}

let gen = generator();
console.log(gen.next(4));
console.log(gen.next());
console.log(gen.next(4));
console.log(gen.next());

/*

    {value: 0, done: false}

    undefined

    {value: 1, done: false}

    4

    {value: 0, done: false}

    undefined

    {value: 1, done: false}

*/

从输出可以看到,再第一次调用 next 方法时,传递给方法的值会被忽略,不会输出任何有关 step 的值。如果没有传递值给 next 方法,step 的值会为 undefined。若将值传递给方法,则 step 的值为传递过来的值,这时,index 的值就会重置,重新从 0 开始计算。

抛出错误

迭代器带有一个 throw 方法用来抛出一个错误,而这个错误可在生成器内部捕获并进行处理。

function* generator() {
    try {
        yield 1;
        yield 2;
        yield 3;
        yield 4;
    } catch (e) {
        yield 99;
    }
}

let gen = generator();
console.log(gen.next()); // {value: 1, done: false}
console.log(gen.next()); // {value: 2, done: false}
console.log(gen.throw()); // {value: 99, done: false}
console.log(gen.next()); // {value: undefined, done: true}

从代码看,当使用迭代器调用 throw 方法抛出一个错误的时候,生成器会返回 99,并中止生成器的状态。

return 语法

没有在生成器内使用 return 语句返回数据,并不意味着不能在生成器内使用 return 语句。

虽然 return 语句还是可以使用的,但要注意的是,这会直接退出生成器。

任务队列

在使用 Ajax 获取数据时,有时候需要先获取一个数据,然后根据返回结果获取第二个数据,再根据第二个返回结果获取第三个,这样一直执行到任务结束。

function send(url, callback) {
    // 发送请求
}

send('https://localhost/', function(result1) {
    let data1 = JSON.parse(result1);
    send(`https://localhost/?params=${data1.params}`, function(result2) {
        let data2 = JSON.parse(result2);
        send(`https://localhost/?params=${data2.params}`, function(result3) {
            let data3 = JSON.parse(result3);
            //...
        })
    })
})

如果任务数很多,以上代码的嵌套就会很累赘,可读性也很差;通过生成器可将以上代码简化

class Task {
    constructor(tasks) {
        var me = this;
        console.log(this); // this 指的是 new Task() 实例化后的对象
        me.index = 0;
        me.tasks = tasks;
        me.generator = me.generator();
        me.generator.next();
    }

    *
    generator() {
        let me = this,
            tasks = me.tasks,
            result = null,
            data = null;
        if (Array.isArray(tasks)) {
            for (let i = 0; i < tasks.length; i++) {
                result = yield me.send(`${tasks[0]}?params=${data ? data.params : ''}`, me.onComplete);
                data = JSON.parse(result);
            }
        }
    }

    onComplete(result) {
        this.generator.next(result)
    }

    send(url) {
        let me = this;
        me.index++;
        console.log(url);
        setTimeout(() => {
            me.onComplete(`{"params": ${me.index}`)
        }, 10)
    }
}

let task = new Task(['https://localhost', 'https://localhost', 'https://localhost']);

异步生成器

异步生成器与同步生成器的主要区别是需要再生成器前面加上 async 语句。

async function* generator() {
    try {
        yield 1;
        yield 2;
        yield 3;
        yield 4;
    } catch (err) {
        yield 99;
    }
}

let gen = generator();
gen.next().then(arg => console.log(arg)); // {value: 1, done: false}
gen.next().then(arg => console.log(arg)); // {value: 2, done: false}
gen.throw().then(arg => console.log(arg)); // {value: 99, done: false}
gen.next().then(arg => console.log(arg)); // {value: undefined, done: true}

for…of循环

for await … of 循环

只能用在异步函数中使用

async function* generator() {
    try {
        yield 1;
        yield 2;
        yield 3;
        yield 4;
    } catch (err) {
        yield 99;
    }
}

let gen = generator();

let fn = async function() {
    for await (const item of gen) {
        console.log(item);
    }
}

fn();

/*
  1
  2
  3
  4
*/

- Book Lists -