Javascript(async/await)で複数APIの非同期処理の結果を取得する

複数のIT勉強会イベントのAPIを使ってカレンダー表示しているサイトはいくつもあるけれど、
リスト表示したり、自分が思った通りのレイアウトで出したかったのでJavascriptを使ってAPIからイベント情報を取得した話。

connpassと、atndと、doorkeeperのAPIを非同期で処理しつつ最後に取得した結果をまとめたい。
それぞれのサービスでは１回で全件取得できないので、複数回に分けてAPIを呼ぶ必要があるが、同じサービス内で非同期で処理すると負荷をかけてしまうのが心配なので、同じAPIに対しては同期的に処理したい。

非同期、同期処理はES2017の[「async / await」](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function)が便利なので使う。IEでは使えない。

APIを読み込んで同期的に結果を変数に格納する場合。

```js
(async () => {
  let events = [];
  let event = [];
  let data = [];

  // connpassのデータ取得
  data = await $.ajax({url: 'connpassのURL', dataType: 'jsonp'});
  event = connpass(data);　// 取得したjsonの結果を共通の形式の配列に変換
  events = events.concat(event);　// イベント情報の配列に追記 
　
  // atndのデータ取得
  data = await $.ajax({url: 'atndのURL', dataType: 'jsonp'});
  event = atnd(data); // 取得したjsonの結果を共通の形式の配列に変換
  events = events.concat(event);　// イベント情報の配列に追記　

  callback(events); // イベント情報の表示を行う処理
　
})();
```
awaitを入れると同期通信になって戻り値が取れるので便利。

一度に全件取得できないので（APIによって100件単位など）、複数回APIを呼び出す。

```js
(async () => {
  let events = [];
  let event = [];
  let data = [];

  // connpassのデータ取得(100件を１０回)
  for (let i = 0; i < 10; i++) {
    data = await $.ajax({url: 'connpassのURL?start=' + (i * 100 + 1), dataType: 'jsonp'});
    event = connpass(data);　// 取得したjsonの結果を共通の形式の配列に変換
    events = events.concat(event);　// イベント情報の配列に追記 
  }

  // atndのデータ取得(100件を１０回)
  for (let i = 0; i < 10; i++) {
    data = await $.ajax({url: 'atndのURL?start=' + (i * 100 + 1), dataType: 'jsonp'});
    event = atnd(data); // 取得したjsonの結果を共通の形式の配列に変換
    events = events.concat(event);　// イベント情報の配列に追記　
  } 

  callback(events); // イベント情報の表示を行う処理
　
})();
```

上記の方法だと、全て２０件とも同期的に順番に動いてしまうので、時間がかかってしまう。
connpassと、atndのAPIに対して非同期に動作すれば、10件分の時間に短縮出来る。
全部非同期にすれば1件分の時間で済むはずだが、同じAPIに対して10件分同時アクセスになってしまい、負荷をかけてしまうかも知れないので、同一APIでは同期的にアクセスする。

```js
(async () => {
  let events = [];
  let event = [];
  let data = [];
  let results = [];

  // connpassのデータ取得(100件を１０回)
  results.push((async () => {
    for (let i = 0; i < 10; i++) {
      data = await $.ajax({url: 'connpassのURL?start=' + (i * 100 + 1), dataType: 'jsonp'});
      event = connpass(data);　// 取得したjsonの結果を共通の形式の配列に変換
      events = events.concat(event);　// イベント情報の配列に追記 
    }
  })());

  // atndのデータ取得(100件を１０回)  
  results.push((async () => {
    for (let i = 0; i < 10; i++) {
      data = await $.ajax({url: 'atndのURL?start=' + (i * 100 + 1), dataType: 'jsonp'});
      event = atnd(data); // 取得したjsonの結果を共通の形式の配列に変換
      events = events.concat(event);　// イベント情報の配列に追記　
    }
  })());
    
  await Promise.all(results); // 非同期処理の結果をまとめて受け取る。
  callback(events); // イベント情報の表示を行う処理
　
})();
```

非同期処理の結果を最後にまとめて取得するのには[Promise.all](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)を使用する。
全体のasyncに加えて、API毎にもasyncを指定することで、異なるAPIは非同期で、同一APIでは同期処理になる。
Promise.allで其々の処理を並列で実行して結果を取得する。

参考URL

* [複数の非同期処理を実行して結果待つ](https://qiita.com/horikeso/items/6830014923216db009b3)


最終的な成果物は以下の通り

* [nightyknite/eventcalendar: connpassとatndのapiを呼んでカレンダーに展開](https://github.com/nightyknite/eventcalendar)

使用したイベントAPIは以下の通り

* [connpass API リファレンス](https://connpass.com/about/api/)
* [API リファレンス - API](https://api.atnd.org/)
* [Doorkeeper API](https://www.doorkeeper.jp/developer/api?locale=en)

APIから取得したイベント情報を配列に整形して、[FullCalendar.js](https://fullcalendar.io/)のライブラリにセットして、カレンダー表示やリスト表示をおこなっている。
FullCalendar.jsはVersion3まではJQueryを使用していたが、最新のVersion4からJQueryを使用しなくなっている。JQueryを使用してるVersion3までの記事が多いので注意。