ES9的新特性:异步遍历Async iteration

简介

在ES6中,引入了同步iteration的概念,随着ES8中的Async操作符的引用,是不是可以在一异步操作中进行遍历操作呢?

今天要给大家讲一讲ES9中的异步遍历的新特性Async iteration。

异步遍历

在讲解异步遍历之前,我们先回想一下ES6中的同步遍历。

根据ES6的定义,iteration主要由三部分组成:

  1. Iterable

先看下Iterable的定义:

interface Iterable {
[Symbol.iterator]() : Iterator;
}

Iterable表示这个对象里面有可遍历的数据,并且需要实现一个可以生成Iterator的工厂方法。

  1. Iterator
interface Iterator {
next() : IteratorResult;
}

可以从Iterable中构建Iterator。Iterator是一个类似游标的概念,可以通过next访问到IteratorResult。

  1. IteratorResult

IteratorResult是每次调用next方法得到的数据。

interface IteratorResult {
value: any;
done: boolean;
}

IteratorResult中除了有一个value值表示要获取到的数据之外,还有一个done,表示是否遍历完成。

下面是一个遍历数组的例子:

> const iterable = ['a', 'b'];
> const iterator = iterable[Symbol.iterator]();
> iterator.next()
{ value: 'a', done: false }
> iterator.next()
{ value: 'b', done: false }
> iterator.next()
{ value: undefined, done: true }

但是上的例子遍历的是同步数据,如果我们获取的是异步数据,比如从http端下载下来的文件,我们想要一行一行的对文件进行遍历。因为读取一行数据是异步操作,那么这就涉及到了异步数据的遍历。

加入异步读取文件的方法是readLinesFromFile,那么同步的遍历方法,对异步来说就不再适用了:

//不再适用
for (const line of readLinesFromFile(fileName)) {
console.log(line);
}

也许你会想,我们是不是可以把异步读取一行的操作封装在Promise中,然后用同步的方式去遍历呢?

想法很好,不过这种情况下,异步操作是否执行完毕是无法检测到的。所以方法并不可行。

于是ES9引入了异步遍历的概念:

  1. 可以通过Symbol.asyncIterator来获取到异步iterables中的iterator。

  2. 异步iterator的next()方法返回Promises对象,其中包含IteratorResults。

所以,我们看下异步遍历的API定义:

interface AsyncIterable {
[Symbol.asyncIterator]() : AsyncIterator;
}
interface AsyncIterator {
next() : Promise<IteratorResult>;
}
interface IteratorResult {
value: any;
done: boolean;
}

我们看一个异步遍历的应用:

const asyncIterable = createAsyncIterable(['a', 'b']);
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
asyncIterator.next()
.then(iterResult1 => {
console.log(iterResult1); // { value: 'a', done: false }
return asyncIterator.next();
})
.then(iterResult2 => {
console.log(iterResult2); // { value: 'b', done: false }
return asyncIterator.next();
})
.then(iterResult3 => {
console.log(iterResult3); // { value: undefined, done: true }
});

其中createAsyncIterable将会把一个同步的iterable转换成一个异步的iterable,我们将会在下面一小节中看一下到底怎么生成的。

这里我们主要关注一下asyncIterator的遍历操作。

因为ES8中引入了Async操作符,我们也可以把上面的代码,使用Async函数重写:

async function f() {
const asyncIterable = createAsyncIterable(['a', 'b']);
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
console.log(await asyncIterator.next());
// { value: 'a', done: false }
console.log(await asyncIterator.next());
// { value: 'b', done: false }
console.log(await asyncIterator.next());
// { value: undefined, done: true }
}

异步iterable的遍历

使用for-of可以遍历同步iterable,使用 for-await-of 可以遍历异步iterable。

async function f() {
for await (const x of createAsyncIterable(['a', 'b'])) {
console.log(x);
}
}
// Output:
// a
// b

注意,await需要放在async函数中才行。

如果我们的异步遍历中出现异常,则可以在 for-await-of 中使用try catch来捕获这个异常:

function createRejectingIterable() {
return {
[Symbol.asyncIterator]() {
return this;
},
next() {
return Promise.reject(new Error('Problem!'));
},
};
}
(async function () {
try {
for await (const x of createRejectingIterable()) {
console.log(x);
}
} catch (e) {
console.error(e);
// Error: Problem!
}
})();

同步的iterable返回的是同步的iterators,next方法返回的是{value, done}。

如果使用 for-await-of 则会将同步的iterators转换成为异步的iterators。然后返回的值被转换成为了Promise。

如果同步的next本身返回的value就是Promise对象,则异步的返回值还是同样的promise。

也就是说会把:Iterable<Promise<T>> 转换成为 AsyncIterable<T> ,如下面的例子所示:

async function main() {
const syncIterable = [
Promise.resolve('a'),
Promise.resolve('b'),
];
for await (const x of syncIterable) {
console.log(x);
}
}
main(); // Output:
// a
// b

上面的例子将同步的Promise转换成异步的Promise。

async function main() {
for await (const x of ['a', 'b']) {
console.log(x);
}
}
main(); // Output:
// c
// d

上面的例子将同步的常量转换成为Promise。 可以看到两者的结果是一样的。

异步iterable的生成

回到上面的例子,我们使用createAsyncIterable(syncIterable)将syncIterable转换成了AsyncIterable。

我们看下这个方法是怎么实现的:

async function* createAsyncIterable(syncIterable) {
for (const elem of syncIterable) {
yield elem;
}
}

上面的代码中,我们在一个普通的generator function前面加上async,表示的是异步的generator。

对于普通的generator来说,每次调用next方法的时候,都会返回一个object {value,done} ,这个object对象是对yield值的封装。

对于一个异步的generator来说,每次调用next方法的时候,都会返回一个包含object {value,done} 的promise对象。这个object对象是对yield值的封装。

因为返回的是Promise对象,所以我们不需要等待异步执行的结果完成,就可以再次调用next方法。

我们可以通过一个Promise.all来同时执行所有的异步Promise操作:

const asyncGenObj = createAsyncIterable(['a', 'b']);
const [{value:v1},{value:v2}] = await Promise.all([
asyncGenObj.next(), asyncGenObj.next()
]);
console.log(v1, v2); // a b

在createAsyncIterable中,我们是从同步的Iterable中创建异步的Iterable。

接下来我们看下如何从异步的Iterable中创建异步的Iterable。

从上一节我们知道,可以使用for-await-of 来读取异步Iterable的数据,于是我们可以这样用:

async function* prefixLines(asyncIterable) {
for await (const line of asyncIterable) {
yield '> ' + line;
}
}

在generator一文中,我们讲到了在generator中调用generator。也就是在一个生产器中通过使用yield*来调用另外一个生成器。

同样的,如果是在异步生成器中,我们可以做同样的事情:

async function* gen1() {
yield 'a';
yield 'b';
return 2;
}
async function* gen2() {
const result = yield* gen1();
// result === 2
} (async function () {
for await (const x of gen2()) {
console.log(x);
}
})();
// Output:
// a
// b

如果在异步生成器中抛出异常,这个异常也会被封装在Promise中:

async function* asyncGenerator() {
throw new Error('Problem!');
}
asyncGenerator().next()
.catch(err => console.log(err)); // Error: Problem!

异步方法和异步生成器

异步方法是使用async function 声明的方法,它会返回一个Promise对象。

function中的return或throw异常会作为返回的Promise中的value。

(async function () {
return 'hello';
})()
.then(x => console.log(x)); // hello (async function () {
throw new Error('Problem!');
})()
.catch(x => console.error(x)); // Error: Problem!

异步生成器是使用 async function * 申明的方法。它会返回一个异步的iterable。

通过调用iterable的next方法,将会返回一个Promise。异步生成器中yield 的值会用来填充Promise的值。如果在生成器中抛出了异常,同样会被Promise捕获到。

async function* gen() {
yield 'hello';
}
const genObj = gen();
genObj.next().then(x => console.log(x));
// { value: 'hello', done: false }

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/es9-async-iteration/

本文来源:flydean的博客

欢迎关注我的公众号:「程序那些事」最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

ES9的新特性:异步遍历Async iteration的更多相关文章

  1. javaScript ES7 ES8 ES9 ES10新特性

    参考文献: https://tuobaye.com/2018/11/27/%E7%BB%86%E8%A7%A3JavaScript-ES7-ES8-ES9-%E6%96%B0%E7%89%B9%E6% ...

  2. jdk8中关于操作集合的一些新特性,遍历和排序操作

    jdk8增加了不少新的东西,在集合操作这块,就有如 lamda表达式,stream,sort,optional等新的类,主要涉及遍历和排序等方面,新特性提升了不少性能,我们开发就是要拥抱新事物,守着老 ...

  3. ES9的新特性:正则表达式RegExp

    简介 正则表达式是我们做数据匹配的时候常用的一种工具,虽然正则表达式的语法并不复杂,但是如果多种语法组合起来会给人一种无从下手的感觉. 于是正则表达式成了程序员的噩梦.今天我们来看一下如何在ES9中玩 ...

  4. JDk8的新特性-流和内部iteration

    JDK8到今天已经出了好几年了  但是在公司能用到新特性的地方还是很少, 去年的时候当时项目老大要求我们用最新的写法来写Java 刚开始看到用stream写出来的代码一脸懵逼,内心就在想  这是Jav ...

  5. servlet3.0 新特性——异步处理

    Servlet 3.0 之前,一个普通 Servlet 的主要工作流程大致如下: 首先,Servlet 接收到请求之后,可能需要对请求携带的数据进行一些预处理: 接着,调用业务接口的某些方法,以完成业 ...

  6. JAVA8新特性--集合遍历之forEach

    java中的集合有两种形式Collection<E>,Map<K,V> Collection类型集合 在JAVA7中遍历有一下几种方式:List<String> l ...

  7. ECMAScript 2018(ES9)新特性简介

    目录 简介 异步遍历 Rest/Spread操作符和对象构建 Rest Spread 创建和拷贝对象 Spread和bject.assign() 的区别 正则表达式 promise.finally 模 ...

  8. 细解JavaScript ES7 ES8 ES9 新特性

    题记:本文提供了一个在线PPT版本,方便您浏览 细解JAVASCRIPT ES7 ES8 ES9 新特性 在线PPT ver 本文的大部分内容译自作者Axel Rauschmayer博士的网站,想了解 ...

  9. ES9新特性

    这篇文章主要介绍ES2018(ES9)的新特性, 以及使用方法 JS是一门跨平台的语言, ES6也就是ECMAScript 2015 花费了5年的时间敲定, 是一次非常大的改版, 之后每年都有一个小版 ...

随机推荐

  1. DAPHNE PATEL:有主见的人,才能活出精彩人生

    有主见的人,会活出什么样子呢?近日,NGK灵石团队技术副总裁DAPHNE 女士给出了答案. DAPHNE PATEL表示,有主见的人,才能活出精彩的人生.为什么这么说呢? DAPHNE PATEL用自 ...

  2. DeFi里的灰度?每月获得高收益?BGV代币初探

    2020年已经接近了尾声,但是DeFi市场的热闹场面并没有停止,或者说,一直在延续.资本市场不断将大批的资金投入到DeFi市场中,以求在这波热潮中赚得一波又一波红利. 美国时间12月21日,Bacca ...

  3. 初学c++,vc++6.0必备!

    文章首发 | 公众号:lunvey 作为一个纯粹的萌新,工作需要,刚接触到c++. 按照以往的经验,配置一个开发环境是首要的,其次便是边学边敲. c++入门书籍寻找了一堆,发现了一个共同点,在Wind ...

  4. C++算法代码——质数的和与积

    题目来自:http://218.5.5.242:9018/JudgeOnline/problem.php?id=1682 题目描述 两个质数的和是S,它们的积最大是多少? 输入 输入文件名为prime ...

  5. 1090 Highest Price in Supply Chain——PAT甲级真题

    1090 Highest Price in Supply Chain A supply chain is a network of retailers(零售商), distributors(经销商), ...

  6. k8s v1.18.2 centos7 下环境搭建

    准备 服务器:3台机器--1台主.2台工作节点,可以使用virtualbox 搭建虚拟机 主机名 centos version ip docker version flannel version 主机 ...

  7. 企业安全_DNS流量监控的技术选型

    方案1 Windows server n ---> packbeat ---> logstash ---> kafka | ┗ ---------> elasticsearch ...

  8. windows本地连接虚拟机上的ubuntu的redis,以及无法连接解决方法(redisDesktopManager Jedis详细步骤)

    一.环境 1.ubuntu20.04 . redis 5.0.7 在ubuntu上下载redis,执行命令 sudo apt install redis 2.redisDesktopManager下载 ...

  9. 【图像处理】使用OpenCV+Python进行图像处理入门教程(二)

    这篇随笔介绍使用OpenCV进行图像处理的第二章 图像的运算,让我们踏上继续回顾OpenCV进行图像处理的奇妙之旅,不断地总结.回顾,以新的视角快速融入计算机视觉的奥秘世界. 2  图像的运算 复杂的 ...

  10. kubernetes cpu限制参数说明

    docker CPU限制参数 Option Description --cpus=<value> Specify how much of the available CPU resourc ...