ES9的新特性:异步遍历Async iteration
ES9的新特性:异步遍历Async iteration
简介
在ES6中,引入了同步iteration的概念,随着ES8中的Async操作符的引用,是不是可以在一异步操作中进行遍历操作呢?
今天要给大家讲一讲ES9中的异步遍历的新特性Async iteration。
异步遍历
在讲解异步遍历之前,我们先回想一下ES6中的同步遍历。
根据ES6的定义,iteration主要由三部分组成:
- Iterable
先看下Iterable的定义:
interface Iterable {
[Symbol.iterator]() : Iterator;
}
Iterable表示这个对象里面有可遍历的数据,并且需要实现一个可以生成Iterator的工厂方法。
- Iterator
interface Iterator {
next() : IteratorResult;
}
可以从Iterable中构建Iterator。Iterator是一个类似游标的概念,可以通过next访问到IteratorResult。
- 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引入了异步遍历的概念:
可以通过Symbol.asyncIterator来获取到异步iterables中的iterator。
异步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的更多相关文章
- 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% ...
- jdk8中关于操作集合的一些新特性,遍历和排序操作
jdk8增加了不少新的东西,在集合操作这块,就有如 lamda表达式,stream,sort,optional等新的类,主要涉及遍历和排序等方面,新特性提升了不少性能,我们开发就是要拥抱新事物,守着老 ...
- ES9的新特性:正则表达式RegExp
简介 正则表达式是我们做数据匹配的时候常用的一种工具,虽然正则表达式的语法并不复杂,但是如果多种语法组合起来会给人一种无从下手的感觉. 于是正则表达式成了程序员的噩梦.今天我们来看一下如何在ES9中玩 ...
- JDk8的新特性-流和内部iteration
JDK8到今天已经出了好几年了 但是在公司能用到新特性的地方还是很少, 去年的时候当时项目老大要求我们用最新的写法来写Java 刚开始看到用stream写出来的代码一脸懵逼,内心就在想 这是Jav ...
- servlet3.0 新特性——异步处理
Servlet 3.0 之前,一个普通 Servlet 的主要工作流程大致如下: 首先,Servlet 接收到请求之后,可能需要对请求携带的数据进行一些预处理: 接着,调用业务接口的某些方法,以完成业 ...
- JAVA8新特性--集合遍历之forEach
java中的集合有两种形式Collection<E>,Map<K,V> Collection类型集合 在JAVA7中遍历有一下几种方式:List<String> l ...
- ECMAScript 2018(ES9)新特性简介
目录 简介 异步遍历 Rest/Spread操作符和对象构建 Rest Spread 创建和拷贝对象 Spread和bject.assign() 的区别 正则表达式 promise.finally 模 ...
- 细解JavaScript ES7 ES8 ES9 新特性
题记:本文提供了一个在线PPT版本,方便您浏览 细解JAVASCRIPT ES7 ES8 ES9 新特性 在线PPT ver 本文的大部分内容译自作者Axel Rauschmayer博士的网站,想了解 ...
- ES9新特性
这篇文章主要介绍ES2018(ES9)的新特性, 以及使用方法 JS是一门跨平台的语言, ES6也就是ECMAScript 2015 花费了5年的时间敲定, 是一次非常大的改版, 之后每年都有一个小版 ...
随机推荐
- HTML Imports & deprecated
HTML Imports & deprecated https://caniuse.com/#search=html imports https://www.chromestatus.com/ ...
- 在多线程编程中不要使用sleep()、usleep()函数
这两个函数是非线程安全的,可能会造成程序卡死. 对于c++程序,建议使用std::this_thread::sleep_for()和std::this_thread::yield()代替. 纯c程序可 ...
- Python 与 excel的简单应用
1.pip openpyxl库: pip install openpyxl -i http://pypi.douban.com/simple --trust-host pypi.douban.com ...
- hadoop环境搭建:高可用
目录 1.硬件配置 2.软件版本 3.准备工作 3.1.配置网络环境 3.2.安装JDK 3.3.安装ZOOKEEPER 4.安装Hadoop 5.启动 6.问题 7.配置文件 1.硬件配置 采用3台 ...
- 微信小程序:自定义组件
为什么要学习自定义组件? 1.用上我自己的单词abc,我希望在页面中展示椭圆形的图片, 2.打开手机淘宝,假如现在要做一个企业级项目,里面有很多页面,首页存在导航模块,点击天猫,进入第二个页面,而第二 ...
- SpringBoot2.x整合JavaMail以qq邮箱发送邮件
本文参考spring官网email接口文档所写. spring-email官方网址:https://docs.spring.io/spring/docs/5.1.8.RELEASE/spring-fr ...
- 读懂一个中型的Django项目
转自https://www.cnblogs.com/huangfuyuan/p/Django.html [前言]中型的项目是比较多的APP,肯会涉及多数据表的操作.如果有人带那就最好了,自己要先了解基 ...
- deepin-terminal改造之路
目录 1. 背景介绍 2. 下载源码 3. 依赖检查及安装 4. 改造之路 4.1 终端透明度快捷键 4.1.1 设置面板增加选项内容 4.1.2 添加配置解析内容 4.1.3 功能实现 4.1.4 ...
- PAT-1119(Pre- and Post-order Traversals)+前序和后序遍历确定二叉树+判断二叉树是否唯一
Pre- and Post-order Traversals PAT-1119 这题难度较大,主要需要考虑如何实现根据前序遍历和后序遍历来确定一颗二叉树 一篇好的文章: 题解 import java. ...
- 操作系统---在内核中重新加载GDT和堆栈
摘要 用BIOS方式启动计算机后,BIOS先读取引导扇区,引导扇区再从外部存储设备中读取加载器,加载器读取内核.进入内核后,把加载器中建立的GDT复制到内核中. 这篇文章的最大价值也许在末尾,对C语言 ...