MENU

JavaScript構文同期・非同期

【JavaScript】 非同期イテレータと非同期ジェネレーターについて理解しよう

更新日:2021/01/11

 

JavaScriptは反復処理を非同期におこなう非同期イテレータという機能があります。
そこで通常のイテレータとの違いを含めて非同期イテレータについてお伝えします。
また発展形である非同期ジェネレーターについてもお伝えします。

 

非同期イテレータとは

非同期イテレータは、通常のイテレータと同じようにnextメソッドを持ちます。
ただしnextメソッドが返す値が異なります。

nextが返す値

通常のイテレータ { value:任意値 , done:真偽値 }
非同期イテレータ { value:任意値 , done:真偽値 }で解決するPromiseオブジェクト

 

通常のイテレータは、nextメソッドを呼び出すと{ value:任意値 , done:真偽値 }形式のオブジェクトを返します。
doneの値がtrueになるまで繰り返します。

 

通常のイテレータの例

 


function myIterator(){
    return {
        count:0,
        next() { //   next:function(){ の短縮系
            return this.count < 3
                ? { value : this.count++ , done:false}
                : {  done:true };
        }
    }
}
function func1(){

    let iterator = myIterator();
    let result = iterator.next();

    while( !result.done ){
        console.log( result.value );
        result = iterator.next();
    }
}
func1();

 

実行すると、コンソールに1,2,3とログ表示されます。

 

非同期イテレータは、nextメソッドを呼び出すとPromiseオブジェクトを返します。
Promiseが解決すると{ value:任意値 , done:真偽値 }形式のオブジェクトを返します。

 

次の例はブラウザ上のボタンが押されるのを待つ非同期イテレータを作成しています。

 

非同期イテレータの例

 


window.addEventListener( "DOMContentLoaded" , ()=> {

    function myAsyncIterator(){
        return {
            count:0,
            next() { //   next:function(){ の短縮系

                return new Promise( resolve =>{
                    if( this.count >= 3 ) resolve( { done: true } );
                    else{
                        const count = this.count ++;
                        const btn = document.getElementById("btn");

                        btn.addEventListener("click",function clickEvent(){
                            btn.removeEventListener("click",clickEvent);
                            resolve( { value : count , done: false} );
                        });
                    }
                });
            }
        }
    }
    async function func2(){

        let asyncIterator = myAsyncIterator();
        let result = await asyncIterator.next();

        while( !result.done ){
            console.log( result.value );
            result = await asyncIterator.next();
        }
    }
    func2();
});

 

HTML

 


<button id="btn">click</button>

 

 

clickを押して動作を確認してみてください。
1,2,3と順番に表示されるはずです。

イテラブルなオブジェクト

 

上の例のイテレータ関数をオブジェクトに実装して、イテラブルなオブジェクトを作成してみます。

 

通常のイテラブルなオブジェクトは、[Symbol.iterator]プロパティにイテレータ関数をセットします。

 

通常のイテラブルなオブジェクト

 


const obj1 ={

    [Symbol.iterator]:myIterator,

};
function func1(){
    for  ( let data of obj1){
        console.log( data );
    }
}
func1();

 

非同期のイテラブルなオブジェクトは、[Symbol.asyncIterator]プロパティに非同期イテレータ関数をセットします。
そして繰り返し処理にfor-await-of構文を使用します。

 

非同期のイテラブルなオブジェクト

 


const obj2 ={
    data1:"A",
    data2:"B",
    data3:"C",

    [Symbol.asyncIterator]:myAsyncIterator,

};
async function func2(){
    for await ( let data of obj2){
        console.log( data );
    }
}
func2();

 

func2関数のfor-await-ofは、obj2からPromiseオブジェクトを取得し、Promiseの結果を待ち、受け取ったオブジェクト ({ value:任意値 , done:真偽値 })のvalueプロパティをdataにセットしています。

 

非同期ジェネレーター

 

ジェネレーター関数の前にasyncを付加すると、非同期ジェネレーターが生成されます。

 

async function* 関数名(){ }

 

通常のジェネレーター関数はyieldキーワードで値を返しますが、非同期ジェネレーター関数はPromiseオブジェクトを返します。
Promiseが解決すると非同期イテレータと同様に{ value:任意値 , done:真偽値 }形式のオブジェクトを返します。

 

本当にそうなのか確認してみます。

 

 

非同期ジェネレーターの動作確認

 


async function* asyncGenerator(){
    yield 1;
}
function fun1(){
    const g = asyncGenerator();
    g.next().then( e=>console.log(e) ); // Object { value: 1, done: false }
    g.next().then( e=>console.log(e) ); // Object { value: undefined, done: true }
}
fun1();

 

g.next()がPromiseオブジェクトを返すので、その値に対してthenメソッドを実行することで、Promiseの結果を取得できます。
想定通りの動作をしていることから、非同期ジェネレーターが{ value:任意値 , done:真偽値 }で解決するPromiseを返していることがわかりますね。

 

上の例は非同期ぽくないので、yield Promiseオブジェクトに変更して、ブラウザ上でボタンを押されるまで待つように修正してみます。
上の例の結果から { value:Promiseオブジェクト , done:真偽値 } が表示されることが予想できます。

 

ブラウザ上でボタンを押されるまで待つ非同期ジェネレーター

 


function btnWait(n){

    return new Promise( resolve => {
        const btn = document.getElementById("btn");
        
        btn.addEventListener("click",function clickEvent(){
            btn.removeEventListener("click",clickEvent);
            resolve(n);
        });
    });
}

async function* asyncGenerator(){
    yield btnWait(1);
}
function fun1(){
    const g = asyncGenerator();
    g.next().then( e=>console.log(e) );
    g.next().then( e=>console.log(e) );
}
fun1();

 

 

clickを押してみてください。
次のような結果になるはずです。

 

{ value: 1, done: false }
{ value: undefined, done: true }

 

yieldでPromiseオブジェクトを指定した場合、その結果を待ってくれます。

 

また、非同期ジェネレーターはそのまま非同期のイテラブルなオブジェクトに適用できます。

 

非同期ジェネレーターを非同期のイテラブルなオブジェクトに適用

 


const obj = {
    [Symbol.asyncIterator]:asyncGenerator,
}
async function fun1(){
    for await( let data of obj){
        console.log( data ); // 結果: 1
    }
}
fun1();

記事の内容について

 

こんにちはけーちゃんです。
説明するのって難しいですね。


「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。

そんなときは、ご意見もらえたら嬉しいです。

ご意見はこちら。
https://affi-sapo-sv.com/info.php

【お願い】

お願い

■このページのURL


■このページのタイトル


■リンクタグ