APIの結果をイベントで掴もう
カスタムイベント、ヨシ!
ESモジュール、ヨシ!
エイジャックス、ヨシ! 1
想定読者
- AjaxはjQueryの$.ajax()とかで呼び出すものだと思ってる人 2
- Promiseで返ってくる結果に
.then()
とか.catch()
とかぶら下げて処理しようとして疲弊してる人 3 - ReactをやろうとしてReduxとか出て来て訳が分からない状態になってる人 4
何がおいしいの
- API呼び出しと結果処理が分離できる
- 「API呼び出し部分はとりあえずスタブにしてボタンとクリックした後の処理を先に書く」とかがやりやすくなる
- カスタムイベントの仕様が決まってしまえば「イベント使う側(画面寄り)」と「API呼び出してイベント投げる側(サーバー寄り)」で担当者も分担できる
- カスタムイベントなのでどんなデータを持ってるのか型情報を書いておくこともできる 5
- イベントなのであちこちでキャッチして使うこともできる
- ログに吐いたり
- DOMに出したり
- 別のイベントのトリガーにして連鎖させたり
結論
Promise.then()
の中で受信データを処理するな!誰かにパスしろパス!
そのためにCustomEvent
を使え!
ついでにESモジュールも使え!! 6
サンプルコードと解説
以下に全文掲載。
ササッとAPIを用意するためにphp
を使ってるのでそのまま試したい人はApache
かIIS
あたりにFastCGIモジュール入れて動かしてください。
そうじゃない人はお好きな言語でサーバーサイドプログラムを書くのもいいし、単にdata.json
とかでもいい。7
画面(index.html)8
<!DOCTYPE html>
<html>
<script src="script/main.js" type="module" async></script>
<body>
<main id="main">
<form>
<button type="button" id="command-button1">CALL[Get-Items]</button>
<button type="button" id="command-button2">CALL[Get-Weather]</button>
<hr/>
<pre id="output"></pre>
</form>
</main>
</body>
</html>
特に捻りはない。
スクリプトをESモジュールとして呼び出しているくらい。
↓こんな画面が出る。シンプル。
script/main.js
import {api} from './api.mjs';
document.addEventListener('click', (event) => {
const target = event.target;
if (target.id === 'command-button1') api.callGetItems();
if (target.id === 'command-button2') api.callGetWeather();
});
document.addEventListener('x-respond', (event) => {
const {eventId, data} = event.detail;
console.log(`SUCCESS ${eventId} data=${JSON.stringify(data, null, 2)}`);
document.getElementById('output').textContent = JSON.stringify(data, null, 2);
});
document.addEventListener('x-reject', event => {
const {eventId, reason} = event.detail;
console.log(`FAILURE ${eventId} reason=${JSON.stringify(reason, null, 2)}`);
document.getElementById('output').textContent = JSON.stringify(reason, null, 2);
});
API呼び出しをする部分は別モジュールに分割している。
ボタンのクリックイベントはdocumentまでバブリングしてから拾うと楽ができる。 9
クリックイベントではAPIを呼び出すだけ。呼び出しっぱなし。
下二つのイベントリスナでカスタムイベントを見ている。
この例ではx-respond
イベントがデータ受信に成功した時に発生するイベントで、x-reject
はAPI呼び出しをミスったかサーバー側でしくじった時に発生するイベントになっている。
実用する場合はもっと長ったらしいイベント名を定義してもいい。
script/api.mjs
export const api = {
callGetItems() {
call('get-items', '/api/get-items.php');
},
callGetWeather() {
call('get-weather', '/api/get-weather.php');
}
};
const call = (eventId, url) => {
fetch(url)
.then(response => {
if (response.ok) return response.json();
throw 'Wow! Something happen!';
})
.then(json =>
raiseRespondEvent(eventId, json))
.catch(reason =>
raiseRejectEvent(eventId, reason));
};
const raiseRespondEvent = (eventId, data) => {
const event = new CustomEvent('x-respond', {
detail: {eventId, data}
});
document.dispatchEvent(event);
};
const raiseRejectEvent = (eventId, reason) => {
const event = new CustomEvent('x-reject', {
detail: {eventId, reason}
});
document.dispatchEvent(event);
};
API呼び出しをするモジュール。
このサンプルでは「レスポンスがOKならJSONデータをx-respond
イベントに流す」という雑なやり方をしている。
実用するならデータ型が同じAPI毎にカスタムイベントを作るとかするとよいと思う。
エラー情報はもっと雑に固定メッセージ投げてるが、実際は役に立つ情報を流そう。
「何か起きたようです」はダメだろう常識的に考えて。
api/get-weather.php
<?php
$geta = rand(0, 100);
$weather = ['weather' =>
($geta > 65 ? '晴れ' : ($geta > 30 ? '曇り' : '雨'))
];
header('Cache-Control: no-cache, no-store, must-revalidate');
header('Content-Type: application/json; charset=utf-8');
echo json_encode($weather);
サーバーAPIその1
辞書によると下駄は英語でも"geta"らしい。
キャッシュして欲しくないのでレスポンスヘッダで指定。
呼び出し側でキャッシュ回避の小細工を尽くすのは止めよう。
api/get-items.php
<?php
$items = [
['id' => '1', 'name' => 'Yamada'],
['id' => '2', 'name' => 'Tanaka'],
['id' => '3', 'name' => 'Nakano'],
['id' => '4', 'name' => 'Koishi'],
['id' => '5', 'name' => 'Uehara'],
];
header('Cache-Control: no-cache, no-store, must-revalidate');
header('Content-Type: application/json; charset=utf-8');
echo json_encode($items);
サーバーAPIその2
名前が意味深に思うかもしれないが、6文字でテキトーに揃えただけなので特に意味はない。
-
ダメとは言わないけど、そのjQuery本当にまだ必要ですか? ↩
-
ayncなイベントハンドラでawaitで繋げてみたりするけど結局大変だよね。 ↩
-
Reduxは便利なReactを気軽に使いたい気持ちにさせないためにあるからね。仕方ない。 ↩
-
サンプルは生JSなので型書いてないけど ↩
-
MS Edgeでもちゃんと使えるぞ!中身がChromeになっちゃう前に試してみよう! ↩
-
FirefoxもChromeもhttpかhttpsじゃないとスクリプトを走らせてくれないので何らかのサーバーはあった方がいい ↩
-
本当はindex.phpだけどphp要素はちっともない ↩
-
そもそもESモジュールだとHTMLの属性で
onclick="click_handle()"
とか書いてもモジュール内なので呼べない。 ↩