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:リクエスト送信時の設定をオブジェクトに渡します。
代表的なプロパティ
プロパティ | 説明 |
---|---|
method | POST | GET | PUT |DELETEなどのリクエストメソッドを文字列で設定する。初期値はGET |
headers | リクエストヘッダーを変更するときに設定する(オブジェクト形式) 例:JSONをサーバーに送信する際 headers: { “Content-Type”: “application/json” } |
body | リクエストのbody部を挿入したい値を設定する 例:JSONをサーバーに送信する際 body: JSON.stringify(obj) |
response:サーバーから返された情報を保持するResponseオブジェクト。代表的なプロパティやメソッドは以下のとおり
プロパティ | 説明 |
---|---|
Response.ok | 200~299のHTTPステータスがサーバーから返された場合にいは、リクエスト成功としてtrueが格納されている。それ以外は、リクエスト失敗としてfalseが返る |
Response.status | HTTPステータスが格納されている |
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:メロン