詳解ES9的新特性之異步遍歷Async iteration
異步遍歷
在講解異步遍歷之前,我們先回想一下ES6中的同步遍歷。
根據(jù)ES6的定義,iteration主要由三部分組成:
Iterable
先看下Iterable的定義:
interface Iterable {
[Symbol.iterator]() : Iterator;
}
Iterable表示這個(gè)對(duì)象里面有可遍歷的數(shù)據(jù),并且需要實(shí)現(xiàn)一個(gè)可以生成Iterator的工廠方法。
Iterator
interface Iterator {
next() : IteratorResult;
}
可以從Iterable中構(gòu)建Iterator。Iterator是一個(gè)類似游標(biāo)的概念,可以通過next訪問到IteratorResult。
IteratorResult
IteratorResult是每次調(diào)用next方法得到的數(shù)據(jù)。
interface IteratorResult {
value: any;
done: boolean;
}
IteratorResult中除了有一個(gè)value值表示要獲取到的數(shù)據(jù)之外,還有一個(gè)done,表示是否遍歷完成。
下面是一個(gè)遍歷數(shù)組的例子:
> 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 }
但是上的例子遍歷的是同步數(shù)據(jù),如果我們獲取的是異步數(shù)據(jù),比如從http端下載下來的文件,我們想要一行一行的對(duì)文件進(jìn)行遍歷。因?yàn)樽x取一行數(shù)據(jù)是異步操作,那么這就涉及到了異步數(shù)據(jù)的遍歷。
加入異步讀取文件的方法是readLinesFromFile,那么同步的遍歷方法,對(duì)異步來說就不再適用了:
//不再適用
for (const line of readLinesFromFile(fileName)) {
console.log(line);
}
也許你會(huì)想,我們是不是可以把異步讀取一行的操作封裝在Promise中,然后用同步的方式去遍歷呢?
想法很好,不過這種情況下,異步操作是否執(zhí)行完畢是無法檢測(cè)到的。所以方法并不可行。
于是ES9引入了異步遍歷的概念:
1.可以通過Symbol.asyncIterator來獲取到異步iterables中的iterator。
2.異步iterator的next()方法返回Promises對(duì)象,其中包含IteratorResults。
所以,我們看下異步遍歷的API定義:
interface AsyncIterable {
[Symbol.asyncIterator]() : AsyncIterator;
}
interface AsyncIterator {
next() : Promise<IteratorResult>;
}
interface IteratorResult {
value: any;
done: boolean;
}
我們看一個(gè)異步遍歷的應(yīng)用:
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將會(huì)把一個(gè)同步的iterable轉(zhuǎn)換成一個(gè)異步的iterable,我們將會(huì)在下面一小節(jié)中看一下到底怎么生成的。
這里我們主要關(guān)注一下asyncIterator的遍歷操作。
因?yàn)镋S8中引入了Async操作符,我們也可以把上面的代碼,使用Async函數(shù)重寫:
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函數(shù)中才行。
如果我們的異步遍歷中出現(xiàn)異常,則可以在 for-await-of 中使用try catch來捕獲這個(gè)異常:
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 則會(huì)將同步的iterators轉(zhuǎn)換成為異步的iterators。然后返回的值被轉(zhuǎn)換成為了Promise。
如果同步的next本身返回的value就是Promise對(duì)象,則異步的返回值還是同樣的promise。
也就是說會(huì)把:Iterable<Promise<T>> 轉(zhuǎn)換成為 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轉(zhuǎn)換成異步的Promise。
async function main() {
for await (const x of ['a', 'b']) {
console.log(x);
}
}
main();
// Output:
// c
// d
上面的例子將同步的常量轉(zhuǎn)換成為Promise。 可以看到兩者的結(jié)果是一樣的。
異步iterable的生成
回到上面的例子,我們使用createAsyncIterable(syncIterable)將syncIterable轉(zhuǎn)換成了AsyncIterable。
我們看下這個(gè)方法是怎么實(shí)現(xiàn)的:
async function* createAsyncIterable(syncIterable) {
for (const elem of syncIterable) {
yield elem;
}
}
上面的代碼中,我們?cè)谝粋€(gè)普通的generator function前面加上async,表示的是異步的generator。
對(duì)于普通的generator來說,每次調(diào)用next方法的時(shí)候,都會(huì)返回一個(gè)object {value,done} ,這個(gè)object對(duì)象是對(duì)yield值的封裝。
對(duì)于一個(gè)異步的generator來說,每次調(diào)用next方法的時(shí)候,都會(huì)返回一個(gè)包含object {value,done} 的promise對(duì)象。這個(gè)object對(duì)象是對(duì)yield值的封裝。
因?yàn)榉祷氐氖荘romise對(duì)象,所以我們不需要等待異步執(zhí)行的結(jié)果完成,就可以再次調(diào)用next方法。
我們可以通過一個(gè)Promise.all來同時(shí)執(zhí)行所有的異步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中創(chuàng)建異步的Iterable。
接下來我們看下如何從異步的Iterable中創(chuàng)建異步的Iterable。
從上一節(jié)我們知道,可以使用for-await-of 來讀取異步Iterable的數(shù)據(jù),于是我們可以這樣用:
async function* prefixLines(asyncIterable) {
for await (const line of asyncIterable) {
yield '> ' + line;
}
}
在generator一文中,我們講到了在generator中調(diào)用generator。也就是在一個(gè)生產(chǎn)器中通過使用yield*來調(diào)用另外一個(gè)生成器。
同樣的,如果是在異步生成器中,我們可以做同樣的事情:
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
如果在異步生成器中拋出異常,這個(gè)異常也會(huì)被封裝在Promise中:
async function* asyncGenerator() {
throw new Error('Problem!');
}
asyncGenerator().next()
.catch(err => console.log(err)); // Error: Problem!
異步方法和異步生成器
異步方法是使用async function 聲明的方法,它會(huì)返回一個(gè)Promise對(duì)象。
function中的return或throw異常會(huì)作為返回的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 * 申明的方法。它會(huì)返回一個(gè)異步的iterable。
通過調(diào)用iterable的next方法,將會(huì)返回一個(gè)Promise。異步生成器中yield 的值會(huì)用來填充Promise的值。如果在生成器中拋出了異常,同樣會(huì)被Promise捕獲到。
async function* gen() {
yield 'hello';
}
const genObj = gen();
genObj.next().then(x => console.log(x));
// { value: 'hello', done: false }
以上就是詳解ES9的新特性之異步遍歷Async iteration的詳細(xì)內(nèi)容,更多關(guān)于ES9的新特性之異步遍歷Async iteration的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Bootstrap的popover(彈出框)2秒后定時(shí)消失的實(shí)現(xiàn)代碼
Bootstrap Popover(彈出框)是使用定制的 Jquery 插件創(chuàng)建的。它可以用來顯示任何元素的一些信息。這篇文章主要介紹了Bootstrap的popover(彈出框)2秒后定時(shí)消失功能,需要的朋友參考下2017-02-02
gridpanel動(dòng)態(tài)加載數(shù)據(jù)的實(shí)例代碼
這篇文章介紹了gridpanel動(dòng)態(tài)加載數(shù)據(jù)的實(shí)例代碼,有需要的朋友可以參考一下2013-07-07
推薦js實(shí)現(xiàn)商品分類到搜索欄友好提示(人機(jī)交互)
推薦js實(shí)現(xiàn)商品分類到搜索欄友好提示(人機(jī)交互)...2007-05-05
原生js提示框并自動(dòng)關(guān)閉(手工關(guān)閉)
今天在寫后臺(tái)交互的時(shí)候原來都是用alert太難看每次都需要點(diǎn)擊一下才可以,比較麻煩所以特整理了幾個(gè)比較好的js提示框代碼,方便提示一下2023-04-04
JavaScript節(jié)點(diǎn)及列表操作實(shí)例小結(jié)
這篇文章主要介紹了JavaScript節(jié)點(diǎn)及列表操作的方法,以實(shí)例的形式較為詳細(xì)的總結(jié)了javascript針對(duì)節(jié)點(diǎn)操作的相關(guān)技巧,并給出了一個(gè)完整的節(jié)點(diǎn)操作方法實(shí)例總結(jié),需要的朋友可以參考下2015-08-08
微信小程序用戶授權(quán)彈窗 拒絕時(shí)引導(dǎo)用戶重新授權(quán)實(shí)現(xiàn)
我們?cè)陂_發(fā)小程序時(shí),如果想獲取用戶信息,就需要獲取用的授權(quán),如果用戶誤點(diǎn)了拒絕授權(quán),我們?cè)趺礃尤フ_的引導(dǎo)用戶重新授權(quán)呢。今天就來給大家講講如果正確的引導(dǎo)用戶授權(quán),需要的朋友可以參考下2019-07-07
Svelte反應(yīng)式變量和函數(shù)工作原理詳解
這篇文章主要為大家介紹了Svelte反應(yīng)式變量和函數(shù)工作原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12

