Ajax通信をする小さなアプリの作成
JavaScript Primer の第二部をやったメモ.
Ajax通信 · JavaScript Primer #jsprimer を実際にやって見た際のメモ.
作るもの
- APIを呼び出して,GitHubからユーザー情報を取得する
- 取得した情報をページに表示する
環境
- macOS catalina
- Node.js v13.13.0
真っ白のページを作る
まず,サーバーを立てて,HTMLにアクセスできる状態にします.
プロジェクトディレクトリの作成
今回はAjax-sample
というディレクトリを作成し,そのディレクトリ以下で作業をします.
HTMLファイルの作成
エントリーポイントとなるhtmlファイルを作成します.index.js
を読み込むだけの真っ白なページです.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Ajax Sample</title>
</head>
<body>
<script src="index.js"></script>
</body>
</html>
JavaScriptファイルの作成
読み込めているかを確認するだけなので,適当な文字列を表示するコードを書きます.
console.log("OK");
サーバーを立てる
Node.jsを使ってサーバーを立てます.Express.jsを使ってもいいですが,今回はhttp.createServer
を使って素朴に実装します.
一部のIDEは特別な設定をしなくてもhtmlをローカルサーバー上で見られるので,この部分は省略できます.
実装
Server.js
const http = require('http');
const fs = require('fs');
const contentType = new Map([
["html", "text/html"],
["js", "text/javascript"],
["css", "text/css"],
["png", "image/png"],
["ico", "image/vnd.microsoft.icon"],
]);
http.createServer(function (req, res) {
const ext = req.url.split('.').pop();
try {
const data = fs.readFileSync('.' + req.url);
res.writeHead(200, {'Content-Type': contentType.get(ext)});
res.write(data);
} catch (e) {
res.writeHead(404, {'Content-Type': 'text/plain'});
res.write(e.message);
}
res.end();
}).listen(8080);
http.createServer
req.url
で指定されたファイルを返します.指定されたファイルが存在しない時,エラーメッセージを返します.
http.createServer(function (req, res) {
const ext = req.url.split('.').pop();
try {
const data = fs.readFileSync('.' + req.url);
res.writeHead(200, {'Content-Type': contentType.get(ext)});
res.write(data);
} catch (e) {
res.writeHead(404, {'Content-Type': 'text/plain'});
res.write(e.message);
}
res.end();
}).listen(8080);
参考:Node.js http.createServer() Method
contentType
[拡張子, contentType]
のMap
.リクエストされたファイルの拡張子によって,適切なContentTypeを返します.
const contentType = new Map([
["html", "text/html"],
["js", "text/javascript"],
["css", "text/css"],
["png", "image/png"],
["ico", "image/vnd.microsoft.icon"],
]);
HTMLの表示
Ajax-sample
├── index.html
├── index.js
└── server.js
Ajax-sample
下でnode server.js
を実行し,http://localhost:8080/index.html
にアクセスすると,以下のようなページが表示されます.コンソールには"OK"が表示されます.ファビコンは用意していないので404が返ります.
今回の実装ではhttp://localhost:8080
以下の文字列をファイル名として処理するので,http://localhost:8080
やhttp://localhost:8080/index
ではファイルが読み込めません.
HTTP通信
次に,API(https://developer.github.com/v3/users/)を呼び出して,レスポンスをコンソールに表示します.
HTTPリクエストを送る関数の作成
fetch
メソッドでHTTPリクエストを作成,送信ができます.このメソッドはPromise
を返すため,then
で成功時と失敗時に呼ばれる関数を登録します.また,レスポンスのデータをjsonに変換するメソッドもPromise
を返すので,jsonをコンソールに表示する関数を渡しておきます.
特定の文字がURIに含まれていると正しく動作しないため,encodeURIComponent()
でエスケープしておきます.
function fetchUserInfo(userId) {
fetch(`https://api.github.com/users/${encodeURIComponent(userId)}`)
.then(
(response) => {
if (response.ok) {
return response.json().then(userInfo =>
console.log(userInfo));
} else {
console.log("Error :", response);
}
},
(error) => {
console.log(error);
}
);
}
参考:encodeURIComponent() - JavaScript | MDN
HTTPリクエストを送るためのボタンを作成
HTMLにボタンを作成し,そのボタンを押すとfetchUserInfo
を呼び出すようにします.ここでは,userId
を埋め込んでいます.
...
<body>
<button onclick="fetchUserInfo('noy72');">Get user info</button>
<script src="index.js"></script>
</body>
...
実際に送ってみる
http://localhost:8080/index.html
にアクセスし,表示されているボタンを押すと,HTTPリクエストが送られます.結果は以下のようにコンソールに表示されます.
データをページに表示する
得られたデータをページに表示するようにします.
今回は,
- アバター
- ユーザー名
- ユーザーID
- フォロー数
- フォロワー数
- レポジトリ数
を表示表示します.
HTMLの組み立て
HTMLは以下のような構造にします.
.
├── ユーザー名,ユーザーID
├── アバター
└── .
├── フォロー数
├── フォロワー数
└──レポジトリ数
Element#innerHTML
プロパティに作成したHTML文字列をセットする方法がありますが,HTMLのエスケープ処理をしたくないので,今回はElement
オブジェクトを生成してツリーを構築します.
結果を表示する部分の作成
button
要素とscript
要素の間に空のdiv
要素を入れておきます.
...
<button onclick="fetchUserInfo('noy72');">Get user info</button>
<div></div> <!-- ここにユーザー情報を表示する -->
<script src="index.js"></script>
...
HTMLの組み立てとDOMへの要素の追加
HTML要素はdocument.createElement
で,単なる文字列はdocument.createTextNode
で作成します.Element#appendChild
で子要素を追加してHTMLを組み立てます.
組み立てたHTMLは,用意しておいたdiv
要素に挿入します.今回はquerySelector
でdiv
要素を受け取り,そこに組み立てたHTMLを挿入します.
function buildHTML(info) {
const user_name = document.createElement("h4");
user_name.appendChild(
document.createTextNode(`${info.name} (@${info.login})`)
);
const avatar = document.createElement("img");
avatar.src = info.avatar_url;
avatar.alt = info.login;
avatar.height = 100;
const list = document.createElement("ul");
const following = document.createElement("li");
following.appendChild(
document.createTextNode(`Following: ${info.following}`)
);
const followers = document.createElement("li");
followers.appendChild(
document.createTextNode(`Followers: ${info.followers}`)
);
const repos = document.createElement("li");
repos.appendChild(
document.createTextNode(`Repos: ${info.public_repos}`)
);
list.appendChild(following);
list.appendChild(followers);
list.appendChild(repos);
const result = document.querySelector('body > div');
result.appendChild(user_name);
result.appendChild(avatar);
result.appendChild(list);
}
上記のコードは以下のようなHTMLを生成します(${}
の部分は展開されます).
<div>
<h4>${info.name} (@${info.login})</h4>
<img src="${info.avater_url}" alt="${info.login}" height="100">
<ul>
<li>Following: ${info.following}</li>
<li>Followers: ${info.followers}</li>
<li>Repos: ${info.public_repos}</li>
</ul>
</div>
buildHTMLを呼ぶ
fetch
して得られたデータをbuildHTML
の引数にし,関数を呼びます.これで,ボタンを押すとデータが表示できるようになります.
if (response.ok) {
response.json().then(userInfo => {
buildHTML(userInfo);
});
} else {
...
Asyncを使う
現在はfetch
メソッドのコールバックでbuildHTML
メソッドを呼び,DOMに変更を加えています.
これを,Async
を使って同期処理のように書き換えてみます.
main関数の追加
直接fetchUserInfo
を呼ばずに,main
関数を通して呼ぶようにします.
function main(){
fetchUserInfo("noy72")
}
...
<body>
<button onclick="main();">Get user info</button>
<div></div>
...
Promiseオブジェクトを返すようにする
fetchUserInfo
でDOMを書き換えるのではなく,Promise
オブジェクトを返すようにします.成功した場合はResponse#json
の戻り値をそのまま返し,失敗した場合はPromise.reject
でエラーを返します.
function fetchUserInfo(userId) {
return fetch(`https://api.github.com/users/${encodeURIComponent(userId)}`).then(
(response) => {
if (response.ok) {
return response.json();
} else {
return Promise.reject(
new Error(`${response.status} ${response.statusText}`)
);
}
}
);
}
mainでPromiseを処理
fetchUserInfo
はPromise
を返すようになったので,main
で処理します.成功した場合は(userInfo) => buildHTML(userInfo)
が実行され,DOMを書き換えます.エラーが発生した場合は,(error) => console.log(error)
が実行されます.
function main() {
fetchUserInfo("noy72")
.then((userInfo) => buildHTML(userInfo))
.catch((error) => console.log(error))
}
これでPromise
を使った処理ができるようになりました.コードは変わりましたが意味的にはPromise
を使っていないのと同じなので,前回実行した時と同様の動作をします.
Asyncを使う
Promise
を返すfetchUserInfo
はawait
することができます.
async function main() {
try {
const userInfo = await fetchUserInfo("noy72");
buildHTML(userInfo);
} catch (error) {
console.log(error);
}
}
fetch
のコールバックで処理を行う実装から,手続的に処理を行う実装になりました.
作成したアプリのリポジトリ
[https://github.com/noy72/JavaScript-Sample-Apps/tree/master/Ajax-sample:title]
まとめ
GithubのAPIを呼び出し,取得したデータを表示するアプリを作成しました.
- サーバーを立てて,HTMLの表示とJavaScriptの実行をした
fetch
を使ってHTTPリクエストを送った- DOMを書き換えて,APIから取得したデータを表示した
Async function
に置き換えた.
現在の実装ではユーザー名を埋め込んでいますが,Promiseを活用する · JavaScript Primer #jsprimer ではユーザー名を変更できるように実装しています.