玩转ES6(五):Iterator、Generator、async/await

IT实战联盟 2018-12-07 14:10:47 ⋅ 714 阅读

1. Iterator 和 for…of 循环

ES6 中有四种数据集合:数组( Array )、对象( Object )、Map 和 Set 。这样就需要一种统一的接口机制,来处理所有不同的数据结构。
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

1.1 Iteration作用:

1. 为各种数据结构,提供一个统一的、简便的访问接口;
2. 使得数据结构的成员能够按某种次序排列;
3. ES6创造了一种新的遍历命令 for...of 循环,Iterator接口主要供 for...of 消费。

1.2 Iterator 的遍历过程

(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。

(2)第一次调用指针对象的 next 方法,可以将指针指向数据结构的第一个成员。

(3)第二次调用指针对象的 next 方法,指针就指向数据结构的第二个成员。

(4)不断调用指针对象的 next 方法,直到它指向数据结构的结束位置。

ES6 规定,默认的 Iterator 接口部署在数据结构的 Symbol.iterator 属性,或者说,一个数据结构只要具有 Symbol.iterator 属性,就可以认为是“可遍历的”(iterable)。

原生具备 Iterator 接口的数据结构如下。

Array
Map
Set
String
TypedArray
函数的 arguments 对象
NodeList 对象

1.3 调用 Iterator 接口的场合

1)解构赋值

对数组和 Set 结构进行解构赋值时,会默认调用 Symbol.iterator 方法。

let set = new Set().add('a').add('b').add('c');
let [x,y] = set;
// x='a'; y='b'

let [first, ...rest] = set;
// first='a'; rest=['b','c'];
2)扩展运算符

扩展运算符(…)也会调用默认的 Iterator 接口。

// 例一
var str = 'hello';
[...str] //  ['h','e','l','l','o']

// 例二
let arr = ['b''c'];
['a', ...arr, 'd']
// ['a''b''c''d']

只要某个数据结构部署了 Iterator 接口,就可以对它使用扩展运算符,将其转为数组。

3)yield*

yield* 后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。

let generator = function() {
  yield 1;
  yield* [2,3,4];
  yield 5;
};

var iterator = generator();

iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }

4)其他场合

由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子。

for...of
Array.from()
Map(), 
Set(), 
WeakMap(), 
WeakSet()(比如 new Map([['a',1],['b',2]]) )
Promise.all()
Promise.race()

2 Generator

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。

Generator

1. 语法上: 
    Generator 函数是一个状态机,封装了多个内部状态。Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。
2. 形式上: 
    Generator 函数是一个普通函数,但是有两个特征:
    一是,function关键字与函数名之间有一个星号;
    二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

可以参看阮一峰老师的--Generator 函数的语法

3. async 函数

用过的人都说好

ES2017 标准引入了 async 函数,使得异步操作变得更加方便

async 函数是什么?
一句话,它就是 Generator 函数的语法糖。

async 函数就是将 Generator 函数的星号( * )替换成 async ,将 yield 替换成 await ,仅此而已。

3.1 async 的改进

async 函数对 Generator 函数的改进,体现在以下四点:

1. 内置执行器。

Generator 函数的执行必须靠执行器,所以才有了 co 模块,而 async 函数自带执行器。也就是说, async 函数的执行,与普通函数一模一样,只要一行。

asyncReadFile();

上面的代码调用了 asyncReadFile 函数,然后它就会自动执行,输出最后结果。这完全不像 Generator 函数,需要调用 next 方法,或者用 co 模块,才能真正执行,得到最后结果。

2. 更好的语义。

async 和 await ,比起星号和 yield ,语义更清楚了。 async 表示函数里有异步操作, await 表示紧跟在后面的表达式需要等待结果。

3. 更广的适用性。

co模块约定, yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以是Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。

4. 返回值是 Promise。

async 函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用 then 方法指定下一步的操作。

进一步说,async 函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而 await 命令就是内部 then 命令的语法糖

1_ko3KtcVSlzpe3RnTRgJaHw (1).jpeg

3.2 基本语法

1. 例子
  1. async 函数返回一个 Promise 对象。

  2. await 命令,正常情况下, await 命令后面是一个 Promise 对象。如果不是,会被转成一个立即 resolve 的 Promise 对象。

// 如下代码,三种情况加上捕获异常,应该很容易理解

asyncDemo();
async function asyncDemo() {
    async function sleep(para{
        return new Promise(function(resolve, reject{
            setTimeout(function() {
                resolve(para * para);
            }, 1000);
        });
    }

    try {
        const result = await sleep(2);
        console.log(result);

        // async使用 返回一个promise
        async function asyncSleep(para{
            // 等待得到最终状态后返回
            return await sleep(para);
        }
        // await 使用
        const result_1 = await asyncSleep(3);
        console.log(result_1);

        asyncSleep(4).then(function(result_2{
            console.log(result_2);
            return Promise.reject("异常")
        });
    } catch (err) {
        // 捕获异常
        console.log(err);

    }
}

答案:

4
9
16
(node:20766) UnhandledPromiseRejectionWarningUnhandled promise rejection (rejection id: 2): 异常

3.3 使用注意点

  1. await 命令后面的 Promise 对象,运行结果可能是 rejected ,所以最好把 await 命令放在 try…catch 代码块中。

  2. 多个 await 命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。

let foo = await getFoo();
let bar = await getBar();

// 写法 1
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法 1.1 
Promise.all([
    new Promise<any>(async (v, e) => v(await test_1().catch(err => e(err)))),
    new Promise<any>(async (v, e) => v(await test_2().catch(err => e(err)))),

])


// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
  1. await 命令只能用在 async 函数之中,如果用在普通函数,就会报错。

3.4 async 函数的实现原理

async 函数的实现原理,就是将** Generator 函数和自动执行器,包装在一个函数里。**

async function fn(args{
  // ...
}
// 等同于
function fn(args{
  return spawn(function* () {
    // ...
  });
}

所有的 async 函数都可以写成上面的第二种形式,其中的 spawn 函数就是自动执行器。

下面给出 spawn 函数的实现,基本就是前文自动执行器的翻版。

function spawn(genF{
  return new Promise(function(resolve, reject{
    var gen = genF();
    function step(nextF{
      try {
        var next = nextF();
      } catch(e) {
        return reject(e);
      }
      if(next.done) {
        return resolve(next.value);
      }
Promise.resolve(next.value).then(function(v{
        step(function() return gen.next(v); });
      }, function(e{
        step(function() return gen.throw(e); });
      });
    }
    step(function() return gen.next(undefined); });
  });
}

更多使用方法,根据业务场景具体分析吧
比如:错误处理、并行执行

---------------END----------------

后续的内容同样精彩

长按关注“IT实战联盟”哦




全部评论: 0

    我有话说:

    ES6(三):数据结构

    Set 是ES6提供的一种新的数据结构,它允许你存储任何类型的唯一值,而且Set中的元素是唯一的。

    温故知新之ES6(二)

    温故知新之ES6 基础类型

    温故知新之ES6(三)

    温故知新之ES6 数组集合和字典

    Node模块之Events模块()

    Node模块之Events模块()

    Rails 6.1 稳定版发布:支持水平分区、改进多数据库支持、Strict Loading

    Rails 6.1 稳定版已发布,其开发团队表示,过去的几个月里他们实现了对多数据库支持的改进、支持在后台销毁关联异步(Associations Async)进程以及将错误转化为对象等。 按数据库

    温故知新之ES6(二)

    紧接本系列上篇

    使用mybatis-generator自动生成代码(附GitHub下载地址)

    大家都知道Mybatis属于半自动ORM,在使用这个框架中,工作量最大的就是书写Mapping的映射文件,并且手动书写很容易出错,那么今天来介绍一下使用Mybatis-Generator来帮我们自动

    「开源推荐」Nginx可视化配置工具—NginxWebUI,小白也可以

    包括http协议转发, tcp协议转发, 反向代理, 负载均衡, ssl证书自动申请、续签、配置等

    Debian 10.6 发布

    Debian 10.6 已发布,这是 Debian 10 "Buster" 的第六个稳定版更新,修复了部分安全问题和 bug。 除了安全方面的更新,还有针对 OpenJDK, Firefox ESR

    移动H5前端大性能优化方案(实战篇)

    移动H5前端大性能优化方案(实战篇)

    您应该避免的个简单的数据库设计错误

    Anith 在他非常成功的文章 Facts and Fallacies about First Normal Form 之后,对个常见的数据库设计错误进行了引人入胜的讨论,尽管使用它们的不幸后果

    OpenSSH 8.6 发布

    OpenSSH 8.6 已发布,OpenSSH 是 100% 完整的 SSH 协议 2.0 版本的实现,并且包括 sftp 客户端和服务器支持,它用于远程登录的主要连接工具。OpenSSH

    Redis 6.2.2 发布

    Redis 6.2.2 现已发布,该版本升级迫切性程度为高。对于那些使用 ACL 和 pub/sub,CONFIG REWRITE,或遭受性能下降影响的用户来说,详见下文: 修复了

    Python 3.8.6 发布

    Python 3.8.6 发布了,它是 Python 3.8 的第六个维护版本。 3.8 系列的维护版本将每两个月定期更新一次,3.8.7 计划于 2020 年 11 月中旬发布。 随着维护版本的

    Redis 6.2.1 发布

    Redis 6.2.1 现已发布,该版本升级迫切性程度为低:修复了编译问题。具体更新内容如下: Bug 修复 修复带有已删除记录的 stream 的 sanitize-dump

    RediSearch 1.6.15 发布,高性能全文搜索引擎

    RediSearch 1.6.15 现已发布,这是1.6 版的维护版本,更新紧急程度较低。具体更新内容如下: Details: Minor enhancements: #1225