クモのようにコツコツと

フロントエンドエンジニア イイダリョウの技術ブログ。略称「クモコツ」

【JS】async/await構文で書いたFetch APIからJSONデータを読み込む

以前、Fetch APIでJSONデータを読み込みました。この時はthen()で処理を繋ぐ書き方をしていました。async/await構文で書くこともできて、そちらの方がよく使われているとのことで、処理を書き換えてみました。それではいきましょう!

【目次】

※参考:前回記事
【JSON】外部JSONファイル化したはっぴいえんどアルバム情報をFetch APIでDOMに読み込む - クモのようにコツコツと

※参考:ネイティブJSでいろいろやってみたシリーズまとめ
qiita.com

async/await構文

API通信はthen()よりasync/await構文

しまぶーさんの「APIを叩く」の解説動画を見ていたら、最近はthen()よりも「async/await構文」の方がよく使われている模様。

youtu.be

どんなものなのか、早速体験したく!

then()の書き方

then()メソッドの書き方。自分にとっては馴染み深い方法w

fetch(url).then(function(res) {
  return res.json();
}).then(function(json) {
  //処理 json.xxxx〜
});
  • fetch()メソッドを実行。引数はAPIのURL
  • then()メソッドで次の処理をつなぐ。引数は無名関数でその引数はレスポンスのres
  • urlのレスポンスresjson()メソッドを実行し、結果(jsonデータ)をreturnで返す
  • then()で次の処理を繋ぐ。引数は無名関数でその引数はjson
  • jsonを使った処理を書く

「then」は「その後」という意味で、then()で繋ぐと「非同期通信が終わったら次の処理を行う」という挙動になる。

ajaxは非同期通信(並行処理)のため処理順を設計するのがポイントになる。非同期通信が終わる前に次の処理をすると「データが見つからない」エラーになる。

async/await構文の書き方

先程のthen()の書き方をasync/await構文に置き換えるとこうなる。

//json読み込み
async function hoge () {
 const res = await fetch(url);
  const json = await res.json();
 // 処理 json.xxxx〜
}
  • 関数のfunction宣言の前にasyncを書いて非同期(async)関数であることを宣言
  • 変数resawaitを書くことで非同期通信が終わった後にfetch()メソッド(引数はurl)を実行。
  • 変数jsonawaitを書くことで非同期通信が終わった後にres.json()を実行
  • resjsonが終わったら次の処理を実行する

asyncは「非同期」という意味で、Ajax(Asynchronous JavaScript + XML)のAと同じ。

awaitは「待つ」という意味で、先程のthen()と同じような役割かな。

then()を使ったFetch API

前回作ったものこちら。thren()を使ったFeatch APIでJSONデータを読み込んでいる。

See the Pen fetch api 03 by イイダリョウ (@i_ryo) on CodePen.

はっぴいえんどのアルバム情報を表示している。

JSコード全体

const url = https://hoge/fuga.json // jsonファイルのURL

//json読み込み
  fetch(url)
    .then(function(response) {
      return response.json();
    })
    .then(function(json) {

    //外枠アクセス
    var spec = document.querySelector('.spec');

    //テンプレ複製
    //(アルバム枚数-1枚)
    for (var i=1; i < json.length; i++) {
      var clone = spec.firstElementChild.cloneNode(true);
      spec.appendChild(clone);
    }
    
    //タイトル、発売日、曲名アクセス
    var specTitle = document.querySelectorAll('.title');
    var specRelease = document.querySelectorAll('.release');
    var specTrack = document.querySelectorAll('.track');
    
    //アルバムデータ入力(アルバム枚数分)
    for (var i=0; i < json.length; i++) {  
      //タイトル入力
      specTitle[i].innerHTML = json[i].title;
      //発売日入力
      specRelease[i].innerHTML = json[i].release;
      //曲名入力(曲数分ループ)
      for(var t=0; t < json[i].track.length; t++) {
        var list = document.createElement('li');
        list.innerHTML= json[i].track[t];
       specTrack[i].appendChild(list);        
      }
    }
  });

※参考:前回記事
【JSON】外部JSONファイル化したはっぴいえんどアルバム情報をFetch APIでDOMに読み込む - クモのようにコツコツと

これをasync/await構文に書き換えてみんと欲す!

Promiseを読み込む

まずはPromiseの読み込みを体験してみる。はて?Promiseとはなんぞや?

作ったもの

作ったものこちら!

See the Pen fetch api -Async/Await 01 by イイダリョウ (@i_ryo) on CodePen.

っておい!画面真っ白じゃないか!(食い気味に)いいんです!!今回の処理はコンソールへの表示です。

JSコード

const url = https://hoge/fuga.json // jsonファイルのURL

//json読み込み
 const res = fetch(url);
 console.log(res);

  /*  .then(function(response) {
      return response.json();
    // 中略
  });
  
  */
  • 変数resfetch()メソッド実行
  • コンソールにres(結果)を表示

やってることはこれだけ。他の処理はコメントアウトした。

コンソール確認

そうするとコンソールにこんなレスポンスが! f:id:idr_zz:20200605182455j:plain はい来た「Promise」!

オブジェクトになってるので開いてみるとめちゃめちゃ深い階層で情報量満載w f:id:idr_zz:20200605182536j:plain

fetch()メソッドを実行するとこのPromiseオブジェクトが返される。この中から処理に必要な情報だけを取り出す必要がある。このままだと使いにくい…

PromiseとFetch()とasync/await

Promiseとは

これまでもPromiseを読み込んでたわけだ。Promiseとはなんぞや。意味は「約束する」。

Promise オブジェクトは非同期処理の最終的な完了処理 (もしくは失敗) およびその結果の値を表現します。

Promise インターフェイスは作成時点では分からなくてもよい値へのプロキシです。Promise を用いることで、非同期アクションが最終的に成功した時の値や失敗した時の理由に対するハンドラーを関連付けることができます。これにより、非同期メソッドは、最終的な値をすぐに返す代わりに、未来のある時点で値を持つ Promise を返すことで、同期メソッドと同じように値を返すことができるようになります。

※参考:Promise - JavaScript | MDN

Promiseとは非同期通信の結果(失敗した理由も含む)全ての情報なんだな。

Fetch()メソッドでPromiseが返される

このPromiseを返すのがFetch()

Fetch API には (ネットワーク越しの通信を含む) リソース取得のためのインターフェイスが定義されています。

fetch() メソッドは必須の引数を1つ取り、取得したいリソースのパスを指定します。成功か失敗かに関わらず、リクエストに対する Response に解決できる Promise を返します。

※参考:Fetch API - Web API | MDN

async/await構文

で、async/awaitです。まずはasync function宣言から。

async function 宣言は、 非同期関数 — AsyncFunction オブジェクトである関数を定義します。非同期関数はイベントループを介して他のコードとは別に実行され、結果として暗黙の Promise を返します。

※参考:async function - JavaScript | MDN

次にawait演算子

await 演算子は、async function によって Promise が返されるのを待機するために使用します。

※参考:await - JavaScript | MDN

ふむふむ。ではthen()メソッドは?

then() メソッドは Promise を返します。最大2つの引数、 Promise が成功した場合と失敗した場合のコールバック関数を取ります。

※参考:Promise.prototype.then() - JavaScript | MDN

やはりawaitthen()は似たような位置づけか。

async/await構文のレスポンス

作ったもの

先程のPromiseからasync/await構文で必要な情報を取り出す

See the Pen fetch api -Async/Await 02 by イイダリョウ (@i_ryo) on CodePen.

また画面上は白いまま。コンソールに表示されているレスポンスが変わっている。

JSコード

先程のfetch()メソッドの部分を変更した。

//json読み込み
async function resJson () {
 const res = await fetch(url);
  const albums = await res.json();
 console.log(albums);
}

resJson();
  • async function宣言で関数resJson()を作成
  • 変数resawait演算子付きfetch()メソッドを実行。引数はurl
  • 変数albumsawait演算子付きres.json()メソッドを実行
  • コンソールにalbums(JSONのレスポンス)を表示
  • resJson()を実行

関数を定義するだけではAPIを読みにいかないので関数を実行もする。(この関数をクリックなどのイベントに入れると実行タイミングを制御できる)

コンソール確認

コンソールはこうなった

JSONデータの中の配列3つが返って来とる! f:id:idr_zz:20200605184843j:plain

はっぴいえんどの3枚のアルバム名がある! f:id:idr_zz:20200605184845j:plain

1つ目を開くとファーストアルバムの詳細情報! f:id:idr_zz:20200605184848j:plain

曲名も全て読み込まれてる! f:id:idr_zz:20200605184851j:plain

async/awaitで取得したJSONデータを処理する

async/awaitで無事にJSONデータは取得できた。次はこのデータを使ってDOM処理をする。

作ったもの

作ったものこちら!

See the Pen fetch api -Async/Await 03 by イイダリョウ (@i_ryo) on CodePen.

今度ははっぴいえんどのアルバム情報が画面にも表示されてる!

JSコード

const url = https://hoge/fuga.json // jsonファイルのURL

//json読み込み
async function resJson () {
  const res = await fetch(url);
  const albums = await res.json();
  console.log(albums);
  
  // DOM
const spec = document.querySelector('.spec');

  //テンプレ複製
    //(アルバム枚数-1枚)
    for (let i=1; i < albums.length; i++) {
      let clone = spec.firstElementChild.cloneNode(true);
      spec.appendChild(clone);
    }
  
  // Dom
  const specTitle = document.querySelectorAll('.title');
const specRelease = document.querySelectorAll('.release');
const specTrack = document.querySelectorAll('.track');

  //アルバムデータ入力(アルバム枚数分)
    for (var i=0; i < albums.length; i++) { 
      //タイトル入力
      specTitle[i].innerHTML = albums[i].title;
      //発売日入力
      specRelease[i].innerHTML = albums[i].release;
      //曲名入力(曲数分ループ)
      for(var t=0; t < albums[i].track.length; t++) {
        var list = document.createElement('li');
        list.innerHTML= albums[i].track[t];
       specTrack[i].appendChild(list);        
      }
    }
}

// 関数実行
resJson();
  • 先程コメントアウトしたDOM処理などをresJson()関数に入れる
  • 前回のthen()のときjsonに繋げていた処理をalbumsに変更する

DOM処理の詳細は前回の記事を参照(jsonalbumsに変更しただけで動いた!)

※参考:前回記事
【JSON】外部JSONファイル化したはっぴいえんどアルバム情報をFetch APIでDOMに読み込む - クモのようにコツコツと

DOM処理などの関数の分離

作ったもの

作ってみてresJson()関数の中の処理が膨らみ過ぎているように感じたので、関数の分離を行った。

See the Pen fetch api -Async/Await 04 by イイダリョウ (@i_ryo) on CodePen.

画面表示上は変更なし

JSコード(全体)

const url = https://hoge/fuga.json // jsonファイルのURL

//json読み込み
async function resJson () {
  const res = await fetch(url);
  const albums = await res.json();
  return albums;
}

// アルバム複製
function albumsClone(albums) {
  // DOM取得
  const spec = document.querySelector('.spec');

  //テンプレ複製
  //(アルバム枚数-1枚)
  for (let i=1; i < albums.length; i++) {
    let clone = spec.firstElementChild.cloneNode(true);
    spec.appendChild(clone);
  } 
}

// アルバム情報入力
function albumsInput(albums) {
  // Dom取得
  const specTitle = document.querySelectorAll('.title');
  const specRelease = document.querySelectorAll('.release');
  const specTrack = document.querySelectorAll('.track');

  //アルバムデータ入力(アルバム枚数分)
    for (var i=0; i < albums.length; i++) { 
      //タイトル入力
      specTitle[i].innerHTML = albums[i].title;
      //発売日入力
      specRelease[i].innerHTML = albums[i].release;
      //曲名入力(曲数分ループ)
      for(var t=0; t < albums[i].track.length; t++) {
        var list = document.createElement('li');
        list.innerHTML= albums[i].track[t];
       specTrack[i].appendChild(list);        
      }
    }
}

async function allAlbums() {
  // アルバム情報取得
  const albums = await resJson();
  // アルバム複製
  albumsClone(albums);
  // アルバム情報入力
  albumsInput(albums);
}

allAlbums();

resJson():JSON読み込み

最初にresJson()関数でJSONデータを読み込む

//json読み込み
async function resJson () {
  const res = await fetch(url);
  const albums = await res.json();
  return albums;
}
  • async/await構文を使ったresJson()関数を定義
  • コンソールに表示してた時と同じ処理
  • 最後にreturnalbumsのレスポンス結果(JSONデータ)を返す

albumsを返すことで外側でもJSONデータの値を使うことができる。

albumsClone():アルバム複製

次はalbumsClone()関数でアルバム複製部分。

// アルバム複製
function albumsClone(albums) {
  // DOM取得
  const spec = document.querySelector('.spec');

  //テンプレ複製
  //(アルバム枚数-1枚)
  for (let i=1; i < albums.length; i++) {
    let clone = spec.firstElementChild.cloneNode(true);
    spec.appendChild(clone);
  } 
}
  • アルバム複製部分を関数として分離
  • 引数でalbumsのデータを読み込む

albumsInput():アルバム情報入力

albumsInput()関数でアルバム情報入力部分の関数も作成

// アルバム情報入力
function albumsInput(albums) {
  // Dom取得
  const specTitle = document.querySelectorAll('.title');
  const specRelease = document.querySelectorAll('.release');
  const specTrack = document.querySelectorAll('.track');

  //アルバムデータ入力(アルバム枚数分)
    for (var i=0; i < albums.length; i++) { 
      //タイトル入力
      specTitle[i].innerHTML = albums[i].title;
      //発売日入力
      specRelease[i].innerHTML = albums[i].release;
      //曲名入力(曲数分ループ)
      for(var t=0; t < albums[i].track.length; t++) {
        var list = document.createElement('li');
        list.innerHTML= albums[i].track[t];
       specTrack[i].appendChild(list);        
      }
    }
}
  • アルバム情報入力部分を関数として分離
  • 引数でalbumsのデータを読み込む

allAlbums():定義した関数を読み込む

最後にallAlbums()関数の中でこれまで定義した関数を読み込む

async function allAlbums() {
  // アルバム情報取得
  const albums = await resJson();
  // アルバム複製
  albumsClone(albums);
  // アルバム情報入力
  albumsInput(albums);
}

allAlbums();
  • async/await構文を使ったallAlbums()関数を定義
  • 変数albumsawait付きでresJson()を実行
  • albumsClone()関数を実行。引数でalbumsを読み込む
  • albumsInput()関数を実行。引数でalbumsを読み込む
  • 最後に定義したallAlbums()自体を実行

APIとの非同期通信をしているのはresJson()なのでawaitをつける。

他の関数は引数でresJson()で取得したalbumsのJSONデータを読み込む。

最後のallAlbums()関数実行もクリックイベントとかの中に入れると実行タイミングを制御できる。

最後に

ということでasync/await構文でのAPI通信を体験できました!then()の書き方に慣れていたんですが、同じことができることがわかったので、asyncawaitに遭遇したら非同期通信だな、と理解できそうです。

自分で書く際にもasync/await構文の方がthenよりも処理を分離しやすいように感じたので、今後も積極的に使って行きたく思います。それではまた!


※参考:ネイティブJSでいろいろやってみたシリーズまとめ
qiita.com