首頁 > web前端 > js教程 > 主體

詳解ES9中的新特性Async iteration

青灯夜游
發布: 2021-04-20 09:49:06
轉載
2454 人瀏覽過

詳解ES9中的新特性Async iteration

在ES6中,引入了同步iteration的概念,隨著ES8中的Async運算子的引用,是不是可以在一非同步操作中進行遍歷操作呢?

今天要跟大家講一講ES9中的非同步遍歷的新特性Async iteration。

非同步遍歷


在講解非同步遍歷之前,我們先回想一下ES6中的同步遍歷。

根據ES6的定義,iteration主要由三個部分組成:

#1、Iterable

先看Iterable的定義:

interface Iterable {
    [Symbol.iterator]() : Iterator;
}
登入後複製

Iterable表示這個物件裡面有可遍歷的數據,並且需要實作一個可以產生Iterator的工廠方法。

2、Iterator

interface Iterator {
    next() : IteratorResult;
}
登入後複製

可以從Iterable建構Iterator。 Iterator是一個類似遊標的概念,可以透過next存取到IteratorResult。

3、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([&#39;a&#39;, &#39;b&#39;]);
const asyncIterator = asyncIterable[Symbol.asyncIterator]();
asyncIterator.next()
.then(iterResult1 => {
    console.log(iterResult1); // { value: &#39;a&#39;, done: false }
    return asyncIterator.next();
})
.then(iterResult2 => {
    console.log(iterResult2); // { value: &#39;b&#39;, 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([&#39;a&#39;, &#39;b&#39;]);
    const asyncIterator = asyncIterable[Symbol.asyncIterator]();
    console.log(await asyncIterator.next());
        // { value: &#39;a&#39;, done: false }
    console.log(await asyncIterator.next());
        // { value: &#39;b&#39;, 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([&#39;a&#39;, &#39;b&#39;])) {
        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(&#39;Problem!&#39;));
        },
    };
}
(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(&#39;a&#39;),
        Promise.resolve(&#39;b&#39;),
    ];
    for await (const x of syncIterable) {
        console.log(x);
    }
}
main();

// Output:
// a
// b
登入後複製

上面的範例將同步的Promise轉換成非同步的Promise。

async function main() {
    for await (const x of [&#39;a&#39;, &#39;b&#39;]) {
        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([&#39;a&#39;, &#39;b&#39;]);
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 &#39;> &#39; + line;
    }
}
登入後複製

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

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

async function* gen1() {
    yield &#39;a&#39;;
    yield &#39;b&#39;;
    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(&#39;Problem!&#39;);
}
asyncGenerator().next()
.catch(err => console.log(err)); // Error: Problem!
登入後複製

异步方法和异步生成器


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

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

(async function () {
    return &#39;hello&#39;;
})()
.then(x => console.log(x)); // hello

(async function () {
    throw new Error(&#39;Problem!&#39;);
})()
.catch(x => console.error(x)); // Error: Problem!
登入後複製

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

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

async function* gen() {
    yield &#39;hello&#39;;
}
const genObj = gen();
genObj.next().then(x => console.log(x));
    // { value: &#39;hello&#39;, done: false }
登入後複製

本文作者:flydean程序那些事

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

更多编程相关知识,请访问:编程视频!!

以上是詳解ES9中的新特性Async iteration的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:flydean的博客
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!