MENU

JavaScript構文同期・非同期

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

更新日:2021/08/25

 

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と順番に表示されるはずです。

 

 

上記のコードではasyncおよびawaiキーワードを使用しています。
これらについては次の記事を参考にしてください。
【JavaScript】 async/awaitの処理の流れを図で解説します

asyncキーワードとイテレーター

 

■非同期関数のおさらい

 

通常の関数にasyncキーワードを付加すると、非同期関数が生成されます。
非同期関数は実行すると、戻り値がPromiseオブジェクトでラップされます。

 

通常関数と非同期関数

 


function func(){
    return 1;
}
console.log( func() ); // 出力結果:1

async function aFunc(){
    return 1;  // new Promise( 1 )が返る
}
aFunc()
    .then( value => console.log( value )); // 出力結果:1

 

 

■イテレータにasyncを付加して非同期イテレータ化

 

イテレータのnextメソッドにasyncキーワードを付加すると、通常の関数と同様に戻り値がPromiseオブジェクトでラップされます。
これにより、非同期イテレータとして機能します。

 

イテレータの非同期イテレータ化

 


function myIterator(){
        return {
            count:0,
            async next() {

                    // 戻り値がPromiseオブジェクトでラップされる
             return this.count < 3
                ? { value : this.count++ , done:false}
                : {  done:true };
                });
            }
        }
}

 

非同期関数内で、他の非同期処理の結果を待つ場合はawaitキーワードを使用します。

 

非同期関数内でawaitキーワードを使用

 


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

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

                        // クリック待ち
                    await new Promise( resolve => {
                        btn.addEventListener("click",function clickEvent(){
                            btn.removeEventListener("click",clickEvent);
                            resolve( );
                        });
                    });
                    return { value : count , done: false};
                }
        }
    }
}

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

 

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

 

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

 

for-of構文を使用すると内部でnextメソッドが呼び出され、返された値が順番に変数に格納されます。

 

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

 


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の [Symbol.asyncIterator]プロパティから非同期イテレーターを取得します。
次にnextを実行してPromiseオブジェクトを取得します。
Promiseの結果を待ち、受け取ったオブジェクト ({ value:任意値 , done:真偽値 })のvalueプロパティを変数dataにセットします。

 

再度nextを呼び出し、doneがtrueになるまで繰り返します。

非同期ジェネレーター

 

ジェネレーター関数の前に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オブジェクトがセットされることが予想できます。

 

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

 


function btnWait(n){

      // Promiseオブジェクトを返す
    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); // btnWaitの戻り値はPromise
}
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オブジェクト返した場合、そのPromiseオブジェクトの結果を待ってくれるのです。

 

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

 

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

 


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

けーちゃんおススメJavaScript入門書

  • スラスラ読める JavaScript ふりがなプログラミング
  • プログラム未経験者がJavaScript始めるならコレ!
    コードを掲載して自分で理解しろという投げっぱなしな入門書とは異なり、コードに一つ一つどんなことをやっているかをふりがなという形式で解説しています。
    それでいてJavaScriptの基礎と応用を学べる良書です。
  • これからWebをはじめる人のHTML&CSS、JavaScriptのきほんのきほん
  • JavaScriptの機能を実践で活かすにはHTMLやCSSの知識が不可欠です。
    しかしそれらの知識があることが前提として書かれている書籍が多い中、この本は総合的な知識を身に着けることができます。
    HTMLやCSSの知識も不安な方には、ぴったりの一冊です
  •  

    入門書の役割は、自分のやりたいことをネットで調べることができるようになるための、基礎的な知識の獲得です。
    まずはこれらの本でしっかりと基礎知識を身につけましょう。
    そしてもっと高度なことや専門的なことはネットで調べ、情報が足りないと感じたら書籍を購入してください。


    記事の内容について

     

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


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

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

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

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

    【お願い】

    お願い

    ■このページのURL


    ■このページのタイトル


    ■リンクタグ


    ※リンクして頂いた方でご希望者には貴サイトの紹介記事を作成してリンクを設置します。
    サイト上部の問い合わせよりご連絡ください。
    ただしサイトのジャンルによっては、お断りさせていただくことがあります。