JavaScript入門 非同期処理 await/async Fetchの解説

最終更新日

ES2017のバージョンからawaitとasyncキーワードを使って、Promiseのthenの処理をより簡潔に記述できるようになりました。Promiseチェーンの記述を簡潔化できます。

async

asyncは、関数の先頭に付けることによって、非同期関数(AsyncFunction)という特殊な関数を定義できます。

構文:asyncの記法

非同期関数の宣言
async function 関数名() {・・・};

無名関数やアロー関数の先頭にも付与できる
someFunction( async () => {・・・} );

オブジェクトやクラスのメソッドにも付与できる
class MyClass {
    async method() { … }
}
const obj = { method: async function() { … } }

非同期関数の違いは、非同期関数のreturnが返す値は必ずPromiseインスタンスになります。たとえば、次の例では、非同期関数のreturnで”hello”を返していますが、それによって返される値はPromiseインスタンスになるため、thenメソッドを続けて記述できます。

asyncはPromiseを返す

async function asyncFunction(){
    return "hello";
}

asyncFunction().then( (returnVal) => { console.log( returnVal ) } ); 
//非同期関数の実行結果はPromiseになるためthenメソッドを呼び出すことが可能

このように、非同期関数でreturnが呼び出されたときに戻り値がPromiseでない場合、暗黙的(自動的)にPromiseでラップされた値(Promise.resolve(“hello”)のように実行したときの値)が返されます。なお、非同期関数でreturnが定義されていない場合には、undefinedがラップされたPromiseインスタンスが返ります。

await

awaitは、Promiseインスタンスの前に記述することで、Promiseのステータスがsettled(fulfilledまたはrejected)になるまで、後続のコードの実行を待機します。なお、awaitは、非同期関数(asyncFunction)でしか使用できません。

構文:awaitの記法

async function 関数名() {
    let  resolvedValue = await prom;
}
  • prom:Promiseインスタンス
  • resolvedValue:Promiseインスタンス内でのresolvedの実引数の値がawaitの結果として返されます。

awaitは、Promise内のresolveの実引数の値を取り出す役割もあります。

awaitはresolveの実引数の値を取り出す

const prom = new Promise( resolve => {
    setTimeout( () => resolve( "この値を取り出します。" ), 1000)
});

async function asyncFunction() {
    const value = await prom;   //resolveの実引数の値がvalueに代入される。また、次行の処理はPromiseオブジェクト(prom)のステータスがfulfilledになるまで待機する
    console.log( value );
}}
asyncFunction();
>この値を取り出します。

また、仮にawaitで受けたPromise内でrejectが実行された場合には、awaitは例外と発生させます。そのため、これまでPromiseのcatchメソッドで行っていた失敗時の処理は、try…catch構文で処理することになります。

Promiseがrejectedになった場合

async function throwError(){
    try {
        await Promise.reject( "Promiseが失敗しました。" );
    } catch( error ){
        console.log( error );
    }
}
throwError();
>Promiseが失敗しました。

await/asyncを使ったPromiseチェーンの書き換え

function promiseFactory(count) {    //※1 この関数はawait/asyncで書き換えることはできない
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            count++;
            console.log(`${count}回目のコールです。時刻:[${new Date().toTimeString()}]`);
            if( count === 3 ) {     //3回目のコールでエラー
                reject(count);
            } else {
                resolve(count);
            }
        }, 1000);
    });
}

/* このコードをexecute()で書き換える
promiseFactory( 0 )
  .then( count => { return promiseFactory( count ); } )
  .then( count => { return promiseFactory( count ); } )
  .then( count => { return promiseFactory( count ); } )
  .catch( errorCount => { 
      console.log(`エラーに飛びました。現在のカウントは${errorCount}です。`);
}).finally(() => {
    console.log( "処理を終了します。" );
});
*/

//await/asyncを使った書き換え
async function execute(){    //※2 awaitを内部で使って言うためasyncを付ける
    try {

        // PromiseFactory内のresolveが呼び出されるまで次の処理を実行しない  ※3
        let count = await promiseFactory( 0 );  //※4 awaitによってresolveの引数の値がcountに代入される
        count = await promiseFactory( count );
        count = await promiseFactory( count );
        count = await promiseFactory( count );
     } catch ( errorCount ) {
         console.error(`エラーに飛びました。現在のカウントは${ errorCount }です。`); //※5 Promiseがrejectedのステータスになった場合はcatchブロックに遷移する 
     } finally {
         console.log( "処理を終了します。" );
     }
}

execute();   //execute()の実行
実行結果
実行結果
  • ※1 await、asyncの書き換えは「Promiseのthenの記述の簡素化」のために使います。そのため、asyncとawaitを使ってpromiseFactoryの書き換えを行うことはできません。
  • ※2 上記コードの書き換えでは、promiseFactoryの非同期処理をawaitを待機しています。awaitキーワードを使用できるのは、async function内のみなので、execute関数の宣言時にasyncキーボードを付けます。
  • ※3 awaitキーワードによって、resolveの引数として渡した値がcountに代入されます。
  • ※4 awaitキーワードによって、resolveの引数として渡した値がcountに代入されます。
  • ※5 Promiseインスタンスの状態がrejectedになったときには、catchブロックに処理が移ります。

このようにして、awaitとasyncを使ってPromiseチェーンを簡潔に記述できます。また、上記のコードでは、execute();をただ単に実行しているだけですが、await execute();のようにしてさらに違う非同期関数内で実行すると、後続の処理を待機できます。

executeの後続の処理を待機させる場合

async function fn(){
    await execute();  
    //executeは非同期関数なので、Promiseを返す。後続の処理は、execute()の実行が完了するまで待機する
}
fn();

Fetch

サーバーからデータやファイルを取得するときに使う、Web APIの一種であるFetch API(fetch関数)について解説。fetch関数は非同期処理になるため、取得したデータを使って処理を行うには、Promiseやawait/asyncを使います。

構文:fetchの記法

fetch( "リクエストURL" [, data] )
    .then( response => response.json() )
    .then( data => { 取得したJSONを使って処理を行うコード } );

リクエストURL:リクエストを送信する先のURLを文字列で渡します。

戻り値:fetch関数を実行すると、response(responseオブジェクト)がPromiseでラップされた値で返されます。

data:リクエスト送信時の設定をオブジェクトに渡します。

代表的なプロパティ

プロパティ説明
methodPOST | GET | PUT |DELETEなどのリクエストメソッドを文字列で設定する。初期値はGET
headersリクエストヘッダーを変更するときに設定する(オブジェクト形式)
例:JSONをサーバーに送信する際
headers: { “Content-Type”: “application/json” }
bodyリクエストのbody部を挿入したい値を設定する
例:JSONをサーバーに送信する際
body: JSON.stringify(obj)

response:サーバーから返された情報を保持するResponseオブジェクト。代表的なプロパティやメソッドは以下のとおり

プロパティ説明
Response.ok200~299のHTTPステータスがサーバーから返された場合にいは、リクエスト成功としてtrueが格納されている。それ以外は、リクエスト失敗としてfalseが返る
Response.statusHTTPステータスが格納されている
Response.headersレスポンスのヘッダー情報がオブジェクトで格納されている
Response.json()レスポンスで返ってきたJSON文字列を処理するときに使う。レスポンスをオブジェクトに変換した値をPromiseでラップしたものを取得する
Response.blob()バイナリデータ(0と1の羅列)を含むレスポンスを処理するときに使う。動画などがこれにあたる。BlobオブジェクトをPromiseでラップしたものを取得する
Response.text()レスポンスの文字列を取得するときに使う。文字列を扱うUSVStringオブジェクトをPromiseでラップしたものを取得する。

Fetchの使用例

サーバーに配置したJSONファイルをfetch関数で取得し、その内容をコンソールに表示する制御を実装します。

fetchを使ったJSONの取得

<script>
    fetch( "sample.json" )  //同じフォルダ内のsample.jsonを相対パスで指定
        .then( response => response.json() )
        .then( data => {
            for( const { key, value } of data ){
                console.log( key + ":" + value );
            }
        } );
   /* await / asyncを使うと以下のように記述できる
   async function myFetch(){
       const response ~ await fetch( "sample.json" );
       const data = await response.json();    //jsonメソッドもPromiseを返す
       for( const { key, value } of data ){
                console.log( key + ":" + value );
       }
   }
   myFetch();
   */
</script>

続いて、index.htmlを作成したときに同じフォルダにsample.jsonを作成して、次のように記述します

[
     {"key": "apple", "value": "リンゴ"},
     {"key": "orange", "value": "オレンジ"},
     {"key": "melon", "value": "メロン"},
]

実行結果

>apple:リンゴ
>orange:オレンジ
>melon:メロン