ES2018异步迭代器

650 阅读3分钟

这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战

前言

ES6的迭代器对象(Iterator)为所有部署了迭代器接口的数据结构提供了统一的遍历方式,但是这种遍历方式只适合同步数据源,对于存在大量交互的页面来说,往往还有许多异步数据源,例如网络请求、文件I/O或流操作等等,然而却没有一种快捷统一的遍历方式。

异步迭代器(Asynchronous iteration)ES2018的新特性之一,它就是专门为了给异步数据源提供的通用遍历接口。

for-await-of

对于并行请求,我们可以使用Promise.all来统一处理,但是对于流这种有序的、分块的数据源,我们不能通过回调的方式一次性处理完,必须使用轮询的方式,数据块到达一次我们就处理一次,如果不使用异步迭代器,要怎么处理呢,这里使用之前的一篇文章5000字带你全面深入理解JS中的Stream API|8月更文挑战 (juejin.cn)里的一个例子:

const readableStream = new ReadableStream({...},{...});
// 创建一个读取器
const reader = readableStream.getReader();
while (true) {
  const { done, value } = await reader.read();
  if (done) {
    console.log('数据读取完毕');
    break;
  }
  console.log('数据块的原始值是:', value);
}

例子里使用一个死循环来轮询流的接收,基于事件循环,因此这个死循环并不会阻塞主线程。这种读取异步数据源的方式不够优雅,我们来使用异步迭代器。

const readableStream = new ReadableStream({...},{...});
// 创建一个读取器
const reader = readableStream.getReader();
for await(const value of reader.read()){
  console.log('数据块的原始值是:', value);
}

可读流内置异步迭代器接口,因此使用for-await-of来遍历每个数据块,我们不用管流什么时候传输完毕,只需专注处理数据本身,像处理同步数据一样,精准且优雅。

部署迭代器接口

要是一个对象能够被for..of遍历,那么它一定要部署了迭代器接口[Symbol.iterator],异步迭代器的部署与同步迭代器一样,它也需要[Symbol.asyncIterator],除此之外,它返回的迭代器对象中的next方法的返回值是个Promise对象:

同步迭代器

 const iterator = {
    [Symbol.iterator]: () => {
     const items = ['J','a','y','l','e','n','L'];
     return {
         next: () =>{
             done: items.length === 0,
             value: items.shift()
       }
     }
   }
 }

异步迭代器

 const asyncIterator = {
    [Symbol.asyncIterator]: () => {
     const items = ['J','a','y','l','e','n','L'];
     return {
         next: () => Promise.resolve({
             done: items.length === 0,
             value: items.shift()
       })
     }
   }
 }

区别显而易见。再来一个例子加深理解,复制到控制台运行:

let asyncIterable = {
  [Symbol.asyncIterator]() {
    return {
      i: 1,
      next() {
        if (this.i <= 10) {
          return new Promise((resolve) => {
            setTimeout(() => resolve({
              value: this.i++,
              done: false
            }), 1000);
          });
        }
        return Promise.resolve({
          done: true
        });
      }
    };
  }
};
(async () => {
  for await (let item of asyncIterable) {
    const val = await item;
    console.log(val);
  }
})();

上面例子每隔一秒输出打印一次数字。

注意事项

扩展运算符...内部会调用遍历对象的迭代器接口,但它只能使用同步迭代器,不能与异步迭代器一起使用,

总结

异步迭代器尤其适合处理数据流,在node中使用非常广泛,结合生成器generator使用便是异步生成器,使用它我们可以用来实现异步I/O的流程控制等等,以及web端的大文件上传,流媒体处理等。

异步iterator与同步iterator在语法上的区别:

同步Iterator异步 Iterator
迭代器接口[Symbol.iterator][Symbol.asyncIterator]
next() 返回的值{value:…, done: true/false}resolve后为 {value:…, done: true/false}Promise