Open4
『Promiseの本』を読んでおさらい
概要
『javascript Promiseの本』という本を読んで気になったことをメモしていく。
1章
いちばん基本の形
function fetchURL(URL) {
return new Promise((resolve, reject) => {
const req = new XMLHttpRequest();
req.open("GET", URL, true);
req.onload = () => {
if (200 <= req.status && req.status < 300) {
// Promise オブジェクトが解決 → fulfilled 状態になる
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = () => {
reject(new Error(req.statusText));
};
req.send();
});
}
const URL = "https://httpbin.org/status/500";
fetchURL(URL)
.then(function onFulfilled(value) {
console.log(value);
})
.catch(function onRejected(error) {
console.error(error);
});
状態について
Promise は、以下の3つの状態しかない。
- Fulfilled:resolve(成功) したとき
- Rejected:reject(失敗)したとき
- Pending:resolveでもrejectでもないとき。初期状態など。
2章
以下の2つは同義。上の書き方は、下の書き方のシンタックスシュガー。
Promise.resolve(42);
new Promise((resolve) => {
resolve(42);
});
Promise chain
Promise は .then()
や .catch()
をメソッドチェーンで書ける。
function taskA() {
console.log("Task A");
}
function taskB() {
console.log("Task B");
}
function onRejected(error) {
console.log("Catch Error: A or B", error);
}
function finalTask() {
console.log("Final Task");
}
const promise = Promise.resolve();
promise
.then(taskA)
.then(taskB)
.catch(onRejected)
.then(finalTask);
then
の使い方
NG
function badAsyncCall() {
const promise = Promise.resolve();
promise.then(() => {
// 何かの処理
return newVar;
});
return promise;
}
OK → Promise.then によって新たに作られた Promise オブジェクトが返される
function anAsyncCall() {
const promise = Promise.resolve();
return promise.then(() => {
// 何かの処理
return newVar;
});
}
Promise.all()
複数の Promise オブジェクトの結果をまとめて取得 & 処理する。
function fetchURL(URL) {
return new Promise((resolve, reject) => {
const req = new XMLHttpRequest();
req.open("GET", URL, true);
req.onload = () => {
if (200 <= req.status && req.status < 300) {
resolve(req.responseText);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = () => {
reject(new Error(req.statusText));
};
req.send();
});
}
// comment, people それぞれの Promise オブジェクトを返す
const request = {
comment() {
return fetchURL("https://azu.github.io/promises-book/json/comment.json").then(JSON.parse);
},
people() {
return fetchURL("https://azu.github.io/promises-book/json/people.json").then(JSON.parse);
}
};
function main() {
return Promise.all([request.comment(), request.people()]);
}
// 実行例
main().then((value) => {
// [comment, people]の順番で出力される
// Promise.all([request.comment(), request.people()]) の実行時は同時だが、
// Promise の結果は、配列に格納された順となる
console.log(value);
}).catch((error) => {
console.error(error);
});
5章
async / await の基本形
ポイントは、await式の右辺にあるPromiseのresolveされた値が左辺の変数へと代入されるので、非同期処理を同期処理のように記述できる点。
// `async`をつけて`fetchBookTitle`関数をAsync Functionとして定義
async function fetchBookTitle() {
// リクエストしてリソースを取得する
const res = await fetch("https://azu.github.io/promises-book/json/book.json");
// レスポンスをJSON形式としてパースする
const json = await res.json();
// JSONからtitleプロパティを取り出す
return json.title;
}
// `async`をつけて`main`関数をAsync Functionとして定義
async function main() {
// `await`式で`fetchBookTitle`の非同期処理が完了するまで待つ
// `fetchBookTitle`がresolveした値が返り値になる
const title = await fetchBookTitle();
console.log(title); // => "JavaScript Promiseの本"
}
main();
- Async Function は必ず Promise を返す
- Async Function内ではawait式が利用できる
await
式
await
式では非同期処理を実行し完了するまで、次の行(次の文)を実行しない。
// async functionは必ずPromiseを返す
async function doAsync() {
// 非同期処理
}
async function asyncMain() {
// doAsyncの非同期処理が完了するまでまつ
await doAsync();
// 次の行はdoAsyncの非同期処理が完了されるまで実行されない
console.log("この行は非同期処理が完了後に実行される");
}
await
式では Fulfilled となった場合、 resolve された値が返り値となる。
async function asyncMain() {
const value = await Promise.resolve(42); // この右辺の返り値は 42 となる
console.log(value); // => 42
}
asyncMain(); // Promiseインスタンスを返す
async / await の try...catch 構文
await 式でのエラーは try…catch 構文でキャッチできる。
async function asyncMain() {
// await式のエラーはtry...catchできる
try {
// `await`式で評価した右辺のPromiseがRejectedとなったため、例外がthrowされる
const value = await Promise.reject(new Error("エラーメッセージ"));
// await式で例外が発生したため、この行は実行されません
} catch (error) {
console.log(error.message); // => "エラーメッセージ"
}
}
asyncMain().then(() => {
console.log("この行は実行されます");
}, (error) => {
// すでにtry...catchされているため、この行は実行されません
});
複数の非同期処理の例
for ループのなかで await
式を用い、結果を配列に push している。
function dummyFetch(path) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (path.startsWith("/resource")) {
resolve({ body: `Response body of ${path}` });
} else {
reject(new Error("NOT FOUND"));
}
}, 1000 * Math.random());
});
}
// 複数のリソースを取得し、レスポンスボディの配列を返す
async function fetchResources(resources) {
const results = [];
for (let i = 0; i < resources.length; i++) {
const resource = resources[i];
const response = await dummyFetch(resource);
results.push(response.body);
}
return results;
}
const resources = ["/resource/A", "/resource/B"];
// リソースを取得して出力する
fetchResources(resources).then((results) => {
console.log(results); // => ["Response body of /resource/A", "Response body of /resource/B"]
});
async 関数の外側ではメインスレッドの処理は進んでいく
Array.forEach() で async 関数をコールバック関数として実行しているが、その外側では fetchResources 関数は処理進めてしまう。
function dummyFetch(path) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (path.startsWith("/resource")) {
resolve({ body: `Response body of ${path}` });
} else {
reject(new Error("NOT FOUND"));
}
}, 1000 * Math.random());
});
}
// リソースを順番に取得する
async function fetchResources(resources) {
const results = [];
resources.forEach(async(resource) => {
const response = await dummyFetch(resource);
results.push(response.body);
});
return results;
}
const resources = ["/resource/A", "/resource/B"];
// リソースを取得して出力する
fetchResources(resources).then((results) => {
// resultsは空になってしまう
console.log(results); // => []
});
解決策として、 Promise.all() を活用する手段がある。
function dummyFetch(path) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (path.startsWith("/resource")) {
resolve({ body: `Response body of ${path}` });
} else {
reject(new Error("NOT FOUND"));
}
}, 1000 * Math.random());
});
}
// 複数のリソースを取得しレスポンスボディの配列を返す
async function fetchResources(resources) {
// リソースをまとめて取得する
const promises = resources.map((resource) => {
return dummyFetch(resource);
});
// すべてのリソースが取得できるまで待つ
// Promise.allは [ResponseA, ResponseB] のように結果が配列となる
const responses = await Promise.all(promises);
// 取得した結果からレスポンスのボディだけを取り出す
return responses.map((response) => {
return response.body;
});
}
const resources = ["/resource/A", "/resource/B"];
// リソースを取得して出力する
fetchResources(resources).then((results) => {
console.log(results); // => ["Response body of /resource/A", "Response body of /resource/B"]
});