ES2018 新特征之:异步迭代器 for-await-of

1. 概述

在 ECMAScript 2015(ES6) 中 JavaScript 引入了迭代器接口(iterator)用来遍历数据。迭代器对象知道如何每次访问集合中的一项, 并跟踪该序列中的当前位置。在 JavaScript 中迭代器是一个对象,它提供了一个 next() 方法,用来返回序列中的下一项。这个方法返回包含两个属性: done 和 value

迭代器对象一旦被创建,就可以反复调用 next()

  
  
  1. function makeIterator(array) {

  2.  let nextIndex = 0;  // 初始索引

  3.  // 返回一个迭代器对象,对象的属性是一个 next 方法

  4.  return {

  5.    next: function() {

  6.      if (nextIndex < array.length) {

  7.        // 当没有到达末尾时,返回当前值,并把索引加1

  8.        return { value: array[nextIndex++], done: false };

  9.      }

  10.      // 到达末尾,done 属性为 true

  11.      return {done: true};

  12.    }

  13.  };

  14. }

一旦初始化, next() 方法可以用来依次访问对象中的键值:

  
  
  1. const it = makeIterator(['j', 'u', 's', 't']);

  2. it.next().value;  // j

  3. it.next().value;  // u

  4. it.next().value;  // s

  5. it.next().value;  // t

  6. it.next().value;  // undefined

  7. it.next().done;   // true

  8. it.next().value;  // undefined

2. 可迭代对象

一个定义了迭代行为的对象,比如在 for...of 中循环了哪些值。为了实现可迭代,一个对象必须实现 @@iterator 方法,这意味着这个对象(或其原型链中的一个对象)必须具有带 Symbol.iterator 键的属性:

String, Array, TypedArray, Map 和 Set 都内置可迭代对象,因为它们的原型对象都有一个 Symbol.iterator 方法。

  
  
  1. const justjavac = {

  2.  [Symbol.iterator]: () => {

  3.    const items = [`j`, `u`, `s`, `t`, `j`, `a`, `v`, `a`, `c`];

  4.    return {

  5.      next: () => ({

  6.        done: items.length === 0,

  7.        value: items.shift()

  8.      })

  9.    }

  10.  }

  11. }

当我们定义了可迭代对象后,就可以在 Array.from、 for...of 中使用这个对象:

  
  
  1. [...justjavac];

  2. // ["j", "u", "s", "t", "j", "a", "v", "a", "c"]

  3. Array.from(justjavac)

  4. // ["j", "u", "s", "t", "j", "a", "v", "a", "c"]

  5. new Set(justjavac);

  6. // {"j", "u", "s", "t", "a", "v", "c"}

  7. for (const item of justjavac) {

  8.  console.log(item)

  9. }

  10. // j

  11. // u

  12. // s

  13. // t

  14. // j

  15. // a

  16. // v

  17. // a

  18. // c

3. 同步迭代

由于在迭代器方法返回时,序列中的下一个值和数据源的 "done" 状态必须已知,所以迭代器只适合于表示同步数据源。

虽然 JavaScript 程序员遇到的许多数据源是同步的(比如内存中的列表和其他数据结构),但是其他许多数据源却不是。例如,任何需要 I/O 访问的数据源通常都会使用基于事件的或流式异步 API 来表示。不幸的是,迭代器不能用来表示这样的数据源。

(即使是 promise 的迭代器也是不够的,因为它的 value 是异步的,但是迭代器需要同步确定 "done" 状态。)

为了给异步数据源提供通用的数据访问协议,我们引入了 AsyncIterator 接口,异步迭代语句( for-await-of)和异步生成器函数。

4. 异步迭代器

一个异步迭代器就像一个迭代器,除了它的 next() 方法返回一个 {value,done} 的 promise。如上所述,我们必须返回迭代器结果的 promise,因为在迭代器方法返回时,迭代器的下一个值和“完成”状态可能未知。

我们修改一下之前的代码:

  
  
  1. const justjavac = {

  2. -  [Symbol.iterator]: () => {

  3. +  [Symbol.asyncIterator]: () => {

  4.     const items = [`j`, `u`, `s`, `t`, `j`, `a`, `v`, `a`, `c`];

  5.     return {

  6. -      next: () => ({

  7. +      next: () => Promise.resolve({

  8.         done: items.length === 0,

  9.         value: items.shift()

  10.       })

  11.     }

  12.   }

  13. }

好的,我们现在有了一个异步迭代器,代码如下:

  
  
  1. const justjavac = {

  2.  [Symbol.asyncIterator]: () => {

  3.    const items = [`j`, `u`, `s`, `t`, `j`, `a`, `v`, `a`, `c`];

  4.    return {

  5.      next: () => Promise.resolve({

  6.        done: items.length === 0,

  7.        value: items.shift()

  8.      })

  9.    }

  10.  }

  11. }

我们可以使用如下代码进行遍历:

  
  
  1. for await (const item of justjavac) {

  2.  console.log(item)

  3. }

如果你遇到了 SyntaxError:forawait(...of...)isonly validinasyncfunctionsandasyncgenerators 错误,那是因为 for-await-of 只能在 async 函数或者 async 生成器里面使用。

修改一下:

  
  
  1. (async function(){

  2.  for await (const item of justjavac) {

  3.    console.log(item)

  4.  }

  5. })();

5. 同步迭代器 vs 异步迭代器

5.1 Iterators

  
  
  1. // 迭代器

  2. interface Iterator {

  3.    next(value) : IteratorResult;

  4.    [optional] throw(value) : IteratorResult;

  5.    [optional] return(value) : IteratorResult;

  6. }

  7. // 迭代结果

  8. interface IteratorResult {

  9.    value : any;

  10.    done : bool;

  11. }

5.2 Async Iterators

  
  
  1. // 异步迭代器

  2. interface AsyncIterator {

  3.    next(value) : Promise<IteratorResult>;

  4.    [optional] throw(value) : Promise<IteratorResult>;

  5.    [optional] return(value) : Promise<IteratorResult>;

  6. }

  7. // 迭代结果

  8. interface IteratorResult {

  9.    value : any;

  10.    done : bool;

  11. }

6. 异步生成器函数

异步生成器函数与生成器函数类似,但有以下区别:

  • 当被调用时,异步生成器函数返回一个对象,"async generator",含有 3 个方法( next, throw,和 return),每个方法都返回一个 Promise,Promise 返回 {value,done}。而普通生成器函数并不返回 Promise,而是直接返回 {value,done}。这会自动使返回的异步生成器对象具有异步迭代的功能。

  • 允许使用 await 表达式和 for-await-of 语句。

  • 修改了 yield* 的行为以支持异步迭代。

示例:

  
  
  1. async function* readLines(path) {

  2.  let file = await fileOpen(path);

  3.  try {

  4.    while (!file.EOF) {

  5.      yield await file.readLine();

  6.    }

  7.  } finally {

  8.    await file.close();

  9.  }

  10. }

函数返回一个异步生成器(async generator)对象,可以用在 for-await-of 语句中。

7. 实现

  • Chakra - 暂未支持

  • JavaScriptCore - Safari Tech Preview 40

  • SpiderMonkey - Firefox 57

  • V8 - Chrome 63

Polyfills

Facebook 的 Regenerator 项目为 AsyncIterator 接口提供了一个 polyfill,将异步生成器函数变成返回 AsyncIterator 的对象 ECMAScript 5 函数。Regenerator 还不支持 for-await-of 异步迭代语法。

Babylon parser 项目支持异步生成器函数和 for-await-of 语句(v6.8.0+)。你可以使用它的 asyncGenerators 插件。

  
  
  1. require("babylon").parse("code", {

  2.  sourceType: "module",

  3.  plugins: [

  4.    "asyncGenerators"

  5.  ]

  6. });

另外,从 6.16.0 开始,异步迭代被包含在 Babel 的 "babel-plugin-transform-async-generator-functions" 下以及 babel-preset-stage-3

  
  
  1. require("babel-core").transform("code", {

  2.  plugins: [

  3.    "transform-async-generator-functions"

  4.  ]

  5. });


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值