#はじめに
はじめまして。この度Qiitaを使い始めました。
Qiitaを通じて技術者の皆さんと交流し、新しい知識を勉強したいと思います。
さて、突然ですが、この間初めて知った「How to」をご紹介いたします。
#結論は最初に出す
ネスト構造(ループ)でAPIを呼び出すときに、Promise.All
がとても役に立ちます。
ただし、各promise
のエラーを慎重に扱うように。
#課題
以下のようなAPIで考えましょう。
Endpoint: /users
Method: GET
Query param: -
Response body:
{
"users": [{
"username": "James Bond",
"userid": 007
}]
}
Endpoint: /userinfo
Method: GET
Query param: userid
Response body:
{
"userid": 007
"username": "James Bond",
"email": "jamesbond@secret.com",
"registration_date": "1962-10-05",
"nationality": "UK"
}
この二つのAPIに対して、以下のことを行います。
-
/users
をたたき、すべてのユーザIDを取得する -
userid
を用いて、/userinfo
をたたき、各ユーザの詳細情報を取得する - すべてが終わったら、元の順番に取得したユーザの情報を出す
#解決策
非同期処理でAPIを呼び出すとき、Promise.all
を用いれば、すべてのAPI操作が終わるとき、次に進むのはできます。
では、Promise.all
の使い方は何でしょうか?
###準備
下記の例を見てみましょう。
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, '1st');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, '2nd');
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 300, '3rd');
});
Promise.all([promise1, promise2, promise3])
.then((values) => {
console.log(values);
});
// expected output: Array ["1st", "2nd", "3rd"]
ご覧の通り、最後の結果は、各promise
の完成時間と関係なく、元通りですね。
ただし、promise
はエラーになったら、どうなるのでしょう。
上の例を少し修正します:
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, '1st');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, '2nd');
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(reject, 300, '3rd'); // resolve → rejectにして、エラーを投げ出す
});
Promise.all([promise1, promise2, promise3])
.then((values) => {
console.log(values);
})
.catch((error) => {
console.log(error); // エラーもコンソールに出す
});
// expected output: "3rd"
ご覧の通り、エラーが出るとき、そのエラーだけが結果に出します。
Promise.all
を用いて、すべての結果を取得したい時、各promise
がエラーを出ないよう、コードを書かなければなりませんね。
###本題
では、課題に戻りましょう。
まずは前提として、API操作関数を下記のようにしていきましょう:
await callApi(param?)
.then((result) => {
// 成功したときの操作
})
.catch((error) => {
// 失敗したときの操作
});
ここで、promise
を活用しましょう。
###例
// UserInfoJsonとは返したユーザ情報データです
// 場合によって別途定義するのは必要かも
const handleUserInfo = async (userid): Promise<UserInfoJson> => {
let userInfo = {
userid: null,
username: null,
email: null,
registration_date: null,
nationality: null
};
await getUserInfo(userid)
.then((result) => {
userInfo = result;
})
.catch((error) => {
console.error(error);
});
return(userInfo);
};
await getUsers() // まずはユーザIDを取る
.then((result) => {
let promises = [];
result.users.map((item, index) => {
promises.push(handleUserInfo(item.userid));
});
Promise.all(promises)
.then((results) => {
// resultsの処理
});
})
.catch((error) => {
// エラーの処理
});
results
にはすべてのユーザ情報が元の順番に入っているので、ちゃんと利用できますね。
#最後に
この記事はあくまでも私個人(初心者として)の経験です。間違っているところ、もっと良い方法がございましたら、ぜひ教えてください。
#参考資料
https://medium.com/@guillermo_26794/asynchronous-fetchs-inside-a-loop-in-react-native-9210058e4b0
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all