TL; DR
非同期処理について、たくさんのいいリソースや記事等がありますので、本記事は単刀直入に実装例から見てみましょう。
非同期処理の基礎概念に興味がある方はこちらの記事をご参考ください。
今回はXMLHttpRequest, Fetch API, Async/Awaitについて、それぞれの実装例と複数の非同期処理の方法を紹介します。(複数の非同期処理向けのPromise Allも記述しています。)
また、HTTPリクエストをシミュレートするためにJSONPlaceholderのエンドポイントを使っています。
XMLHttpRequest (XHR)
現在はよりモダンなFetch APIがあるため、XMLHttpRequestを若干非推奨としていますが、歴史的な理由により、例え既存のスクリプトをサポートする必要がある場合等、そのままXMLHttpRequestが使われている場合もあります。
リクエストをするためには、4 つのステップが必要です。
- XMLHttpRequestを作成します
- リクエストを初期化します
- リクエストを送ります
- (イベントをリッスン)レスポンスに対する処理します
非同期のリクエスト
基本構文
const request = new XMLHttpRequest();
request.open('GET', 'https://jsonplaceholder.typicode.com/users');
request.send();
request.addEventListener('load', () => {
if (request.status !== 200) console.log('error');
const data = JSON.parse(request.responseText);
console.log(data);
});
load
イベント以外はreadystatechange
イベントも使えます。
Ready Stateの値について、以下の表を参照してください。
値 | 状態 | 説明 |
---|---|---|
0 | UNSENT | open()まだ呼ばれていない。 |
1 | OPENED | open()が呼び出し済み。 |
2 | HEADERS_RECEIVED | レスポンスヘッダを受け取った。 |
3 | LOADING | レスポンスはロード中。 |
4 | DONE | リクエスト完了。 |
ここで、一つ注意点があります。
URLに誤りがある場合、リクエストはそのまま実行されます。つまり、リクエスト完了したらステータス 4 になります。なので、request.status
は200
かどうかも確認なければならないです。
const request = new XMLHttpRequest();
request.open('GET', 'https://jsonplaceholder.typicode.com/users');
request.send();
request.addEventListener('readystatechange', () => {
if (request.readyState !== 4) return;
if (request.status !== 200) console.log('error');
// request.readyState === 4 && request.status === 200 場合
const data = JSON.parse(request.responseText);
console.log(data);
});
これで、リクエストができましたが、もし他のURLにリクエストしたい場合、コードが重複される可能性があります。重複したコードを避けるため、関数に入れてリファクタリングします。
関数でリファクタリング
const getUsers = (url, callback) => {
const request = new XMLHttpRequest();
request.addEventListener('readystatechange', () => {
if (request.readyState !== 4) return;
if (request.status !== 200) callback('error', undefined);
const data = JSON.parse(request.responseText);
callback(undefined, data);
});
request.open('GET', url);
request.send();
};
getUsers('https://jsonplaceholder.typicode.com/users', (err, data) => {
if (err) console.log(err);
console.log(data);
});
ひとつ注意すべき点として、このコードで複数の非同期処理を実行する時にコールバック地獄になるかもしれませんので、以下のようなPromiseを使って、複数の非同期処理のためリファクタリングします。
Promiseでリファクタリング
const getRequest = (url) => {
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.addEventListener('readystatechange', () => {
if (request.readyState !== 4) return;
if (request.status !== 200) reject('error');
const data = JSON.parse(request.responseText);
resolve(data);
});
request.open('GET', url);
request.send();
});
};
getRequest('https://jsonplaceholder.typicode.com/users')
.then((data) => console.log(data))
.catch((err) => console.log(err));
複数の非同期のリクエスト
const getRequest = (url) => {
return new Promise((resolve, reject) => {
const request = new XMLHttpRequest();
request.addEventListener('readystatechange', () => {
if (request.readyState !== 4) return;
if (request.status !== 200) reject('error');
const data = JSON.parse(request.responseText);
resolve(data);
});
request.open('GET', url);
request.send();
});
};
getRequest('https://jsonplaceholder.typicode.com/users')
.then((data) => {
console.log('Request 1: Users', data);
return getRequest('https://jsonplaceholder.typicode.com/posts');
})
.then((data) => {
console.log('Request 2: Posts', data);
return getRequest('https://jsonplaceholder.typicode.com/comments');
})
.then((data) => console.log('Request 3: Comments', data))
.catch((err) => console.log(err));
FetchAPI
Fetch API は XMLHttpRequestよりもっと柔軟な操作が可能です。
非同期のリクエスト
fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => response.json())
.then((data) => console.log(data))
.catch((err) => console.log(err));
複数の非同期のリクエスト
複数のリクエストと言ったら、多分直感的に以下のようなネストする事がなりますが、ネストが深くなると構造が把握しづらくなります。
以下の例は三つのリクエストをネストしたサンプルコードです。
悪い例:
fetch('https://jsonplaceholder.typicode.com/users')
.then((userResponse) => userResponse.json())
.then((userData) => {
console.log(userData);
fetch('https://jsonplaceholder.typicode.com/posts')
.then((postResponse) => postResponse.json())
.then((postData) => {
console.log(postData);
fetch('https://jsonplaceholder.typicode.com/comments')
.then((commentResponse) => commentResponse.json())
.then((data) => console.log(data))
.catch((commentData) => console.log(commentData));
})
.catch((postError) => console.log(postError));
})
.catch((userError) => console.log(userError));
ネストを避けるため、2回目と3回目のfetch(url)
前にreturn
をすると、以下のことができます。
-
fetch(url)
にいるコードブロックはPromiseとして返しますので、.then
メソッドチェーンを使用できます。 - 一つの
catch
で全てリクエストのエラーをキャッチできます。
fetch('https://jsonplaceholder.typicode.com/users')
.then((userResponse) => userResponse.json())
.then((userData) => {
console.log(userData);
return fetch('https://jsonplaceholder.typicode.com/posts');
})
.then((postResponse) => postResponse.json())
.then((postData) => {
console.log(postData);
return fetch('https://jsonplaceholder.typicode.com/comments');
})
.then((commentResponse) => commentResponse.json())
.then((commentData) => console.log(commentData))
.catch((err) => console.log(err));
Async/Await + Fetch APIを使えば、もっと簡潔なコードを書けます。
Async/Await + Fetch API
非同期のリクエスト
const getRequest = async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (response.status !== 200) throw new Error('Failed to fetch');
const data = await response.json();
return data;
};
getRequest()
.then((data) => console.log(data))
.catch((err) => console.log(err.message));
非同期関数の返す値はまだPromiseですので、.then
を使わなと戻ったデータを使えないです。
複数の非同期のリクエスト
const getRequest = async () => {
const checkStatus = (res) => {
if (res.status !== 200) throw new Error('Failed to fetch');
};
const userResponse = await fetch('https://jsonplaceholder.typicode.com/users');
checkStatus(userResponse);
const userData = await userResponse.json();
const postResponse = await fetch('https://jsonplaceholder.typicode.com/posts');
checkStatus(postResponse);
const postData = await postResponse.json();
const commentResponse = await fetch('https://jsonplaceholder.typicode.com/comments');
checkStatus(commentResponse);
const commentData = await commentResponse.json();
return { userData, postData, commentData };
};
getRequest()
.then((data) => console.log(data))
.catch((err) => console.log(err.message));
Async/Await + Fetch APIを使うと、コードの可読性を高められます。
Promise All
Promise.all()
メソッドは複数の非同期のリクエストをすることができます、そしてまとめて単一の Promise を返します。
const getRequest = async (url) => {
const response = await fetch(url);
if (response.status !== 200) throw new Error('Failed to fetch');
return response.json();
};
const userPromise = getRequest('https://jsonplaceholder.typicode.com/users');
const postPromise = getRequest('https://jsonplaceholder.typicode.com/posts');
const commentPromise = getRequest('https://jsonplaceholder.typicode.com/comments');
Promise.all([userPromise, postPromise, commentPromise])
.then((data) => console.log(data))
.catch((err) => console.log(err));
参考文献
ここまでお読みいただきありがとうございました。
もし文法ミスや誤字がありましたら、編集リクエストでご意見いただければと思います。