コード設計を学びたい人におすすめの本はこちら
記事をご覧いただきありがとうございます。
この記事では、初学者によっての最初の壁であるJavaScriptの非同期処理について解説します。
JavaScriptの非同期処理を一度で理解するのは難しいので、何度も繰り返し学習することで身につけましょう!
英語で学習したい方はまずはこちらの記事をご覧ください。
英文の読み方について解説しています。
コールバック関数
コールバック関数は、他の関数に引数として渡され、ある処理が終了した際に呼び出される関数で、非同期処理を制御するために利用されます。
以下は、コールバック関数を利用した同期処理と非同期処理 (setTimeoutを利用) の例です。
英語で学ぶ
A callback function is a function [ passed ( as an argument ) ( to another function, )] [ which is then invoked ( after a certain process completes. ] ( ② ) It is commonly used ( to control asynchronous operations. ) ( -③ )
< Functions [ that call callback functions ]> are not necessarily executed asynchronously. ( -③ ) < Whether it is synchronous or asynchronous > depends ( on the specifications [ of the function [ that receives the callback function. ]]) ( ① )
Below are examples [ of synchronous processing and asynchronous processing ] [ using callback functions. ](using setTimeout) ( ② )
const asynchronousCallback = () => {
console.log("非同期的なコールバック関数が実行されました。");
};
const synchronousCallback = () => {
console.log("同期的なコールバック関数が実行されました。");
};
const execAsyncProcess = (callback) => {
// setTimeoutを使用して非同期処理を実行
setTimeout(() => {
callback();
}, 1000);
};
const execSyncProcess = (callback) => {
callback();
};
// execAsyncProcess関数を呼び出し、非同期的なコールバック関数が実行される
execAsyncProcess(asynchronousCallback);
// execSyncProcess関数を呼び出し、同期的なコールバック関数が実行される
execSyncProcess(synchronousCallback);
console.log("プログラム完了");
実行の流れは以下です。
同期的なコールバック関数が実行されました。
プログラム完了
非同期的なコールバック関数が実行されました。(1秒後に実行される)
コールバック関数の課題
コールバック関数を利用することで、非同期処理を制御することは可能ですが、処理内容が多くなると、ネストが深まり、コードが読みづらくなるという課題が発生します。
例えば、非同期処理を含む関数Aがあり、その関数Aが完了した後に何らかの処理を行うためのコールバック関数Bが必要な場合、次のように記述になります。
英語で学ぶ
It is possible ( to control asynchronous processing ( by using callback functions, )) ( ② ) but ( as the number [ of processing operations increases, ]) the problem arises [ that the nesting becomes deep and the code becomes difficult ( to read. )] ( ① )
< The reason [ for the deep nesting ]> is [ that < the code [ that is executed ( when the asynchronous operation completes the callback function )]> must be nested ( within the original code. )] ( ② )
( For example, ) ( if you have a function A [ that includes asynchronous processing ] and you need a callback function B [ to perform some processing ( after function A completes, )]) you would write it ( as follows. ) ( ③ )
関数A((dataA) => {
// 関数Aの完了後に行う処理
関数B(dataA, (dataB) => {
// 関数Bの完了後に行う処理
関数C(dataB, (dataC) => {
// さらにネストが続く...
});
});
});
このように、非同期処理がネストされると、見通しが悪くなります。この構造は「コールバック地獄」と呼ばれ、可読性が低下し、コードの保守性が損なわれる原因となります。
そこでPromiseの登場です。Promiseを利用することで、よりシンプルに非同期処理を制御することができます。
英語で学ぶ
( When asynchronous operations are nested ( in this way, )) visibility becomes poor. ( ② ) This structure is called “callback hell,” ( -⑤ ) and it reduces readability ( ③ ) and makes your code less maintainable. ( ⑤ )
That‘s ( the place (省略)) [ where Promise comes ( in. )] ( By using Promise, ) ( ② ) you can control asynchronous processing more simply. ( ③ )
リスニングする
Promiseとは
JavaScriptのPromiseとは、非同期処理の最終的な結果を表すオブジェクトです。Promiseを英語の意味から考えてみましょう。
Promise (JavaScript) は、英語の文脈で「約束」を意味し、この概念が非同期処理にどのように関連しているかを考えると、まさに処理の順番や結果を制御する「約束」と捉えることができます。
この「約束」は、非同期処理が完了したときに成功または失敗のいずれかの結果を返すことを指しています。Promiseは、処理の進捗を監視し、成功か失敗かの結果を取得して、処理を順番に実行する手段を提供しています。
例えば、ネットワーク通信などの非同期処理では、処理の順番が不確定であるため、それが完了するまで待つ必要があります。Promiseは、非同期処理が完了するのを待機し、その結果を扱うことで、処理の順番を制御することができます。
英語で学ぶ
A JavaScript Promise is an object [ that represents the final result [ of an asynchronous process. ]] ( ② ) Let’s ( let us ) think ( about the meaning [ of Promise ] ( in English. )) ( ⑤ )
Promise ( in JavaScript ) means “promise” [ in the English context, ] ( ③ ) and ( considering < how this concept is related ( to asynchronous processing, )>] it can be thought ( of as a “promise” ) [ that controls the order and outcome of processing. ] ( -③ )
This “promise” refers ( to returning a result [ of either success or failure ] ( when the asynchronous process completes. )) ( ① ) Promise provides a way [ to monitor the progress [ of operations, ]] obtain success or failure results, and execute processing ( in sequence. ) ( ③ )
( For example, ) ( in asynchronous processing ( such as network communication, )) < the order [ of processing ]> is uncertain, ( ② ) so you must wait until the processing completes. ( ③ ) Promise allows you < to control the order [ of processing ] ( by waiting ( for asynchronous processing to complete ) and handling the results. )> ( ⑤ )
Promiseオブジェクトのステータス
Promiseオブジェクトはは3つの状態のいずれかを保持します。
- pending… 処理が終わるのを待っている状態
- fulfilled… 処理が成功して完了した状態
- rejected… 処理が失敗した状態
英語で学ぶ
A Promise object can be ( in one of three states. )
- pending… The state of waiting for the operation to finish.
- fulfilled… The state where the process has successfully completed
- rejected… The state of the process having failed.
Promiseの引数
- resolve… 非同期処理が成功した場合に呼び出される関数です。この関数には、非同期処理の結果や成功時のデータを渡し、Promiseオブジェクトは成功した状態(Fulfilled)に遷移します。
- reject… 非同期処理が失敗した場合に呼び出される関数です。この関数には、非同期処理の失敗理由やエラーオブジェクトを渡し、Promiseオブジェクトは失敗した状態(Rejected)に遷移します。
英語で学ぶ
resolve… resolve is a function [ that is invoked ( when an asynchronous operation is successful. )] ( ② ) This function receives the result or successful data ( from the asynchronous operation, ) ( ③ ) and the Promise object will transition ( to the successful state (Fulfilled). ) ( ① )
reject… reject is a function [ that is invoked ( when an asynchronous operation fails. )] ( ② ) This function receives the reason [ for the failure ] or an error object ( from the asynchronous operation, ) ( ③ ) and the Promise object will transition ( to the rejected state. ) ( ① )
リスニングする
Promiseのサンプルコード
const execAsyncProcess = (result) => new Promise((resolve, reject) => {
setTimeout(() => {
if (result) {
resolve("非同期処理成功"); // 成功時はresolveを呼び出す
} else {
reject("非同期処理失敗"); // 失敗時はrejectを呼び出す
}
}, 3000);
});
const myPromise = (promise) => {
promise
.then((result) => {
// Promiseが成功(fulfilled)した場合の処理
console.log("成功:", result);
})
.catch((error) => {
// Promiseが失敗(rejected)した場合の処理
console.error("エラー:", error);
})
.finally(() => {
console.log(`処理が完了しました。`);
});
};
// Promiseの実行と処理のハンドリング
const fulfilledPromise = execAsyncProcess(true);
console.log(fulfilledPromise);
const rejectedPromise = execAsyncProcess(false);
console.log(rejectedPromise);
// myPromise関数を使用して処理を実行
myPromise(fulfilledPromise, "成功");
myPromise(rejectedPromise, "失敗");
// thenメソッドやcatchメソッドに渡したコールバック関数は非同期処理が終わるまで実行されない
console.log("非同期処理の終了を待っています。");
実行結果は以下です。
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: "非同期処理成功"
[[Prototype]]: Promise
[[PromiseState]]: "rejected"
[[PromiseResult]]: "非同期処理失敗"
非同期処理の終了を待っています。
成功: 非同期処理成功
処理が完了しました。
エラー: 非同期処理失敗
処理が完了しました。
コードの解説は以下です。
- execAsyncProcess 関数でPromiseオブジェクトを生成。
- myPromise 関数は、Promiseが成功した場合 (fulfilled)、失敗した場合 (rejected)、最終的にどちらでも実行するコールバック関数を登録します。then は、非同期処理が成功(fulfilled)した際に実行されるコールバック関数を登録するメソッドで、catch は失敗時の処理を行うコールバック関数を登録します。
- fulfilledPromise と rejectedPromise にそれぞれ成功・失敗のPromiseを代入します。
- myPromise 関数を使用して fulfilledPromise と rejectedPromise の処理を行う。
英語で学ぶ
< The explanation [ of the code ]> is below. ( ② )
- Generate a Promise object [ with the execAsyncProcess function. ] ( ③ )
- The myPromise function registers callback functions [ to be executed ( when a Promise is fulfilled or rejected, and ultimately, in either case. )] ( ③ ) The then method is used ( to register a callback function [ that will be executed ( when the asynchronous operation succeeds (fulfilled), )]) ( while the catch method is used ( to register a callback function [ to handle actions [ in the event of a failure (rejected). ]])) ( -③ )
- Assign [ fulfilled and rejected ] promise ( to fulfilledPromise and rejectedPromise, ) respectively. ( ③ )
- Use the myPromise function ( to process fulfilledPromise and rejectedPromise. ) ( ③ )
コールバック関数とPromiseの比較
コールバック関数とPromiseを同じコードで比較してみましょう。
英語で学ぶ
Let’s ( let us ) compare [ callback functions and promise ( in the same code. )] ( ⑤ )
const asyncTask = (taskName, callback) => {
setTimeout(() => {
console.log(`${taskName} 完了`);
callback();
}, 1000);
};
// 非同期処理の実行
asyncTask("非同期タスク1", () => {
asyncTask("非同期タスク2", () => {
asyncTask("非同期タスク3", () => {
console.log("すべての非同期タスク完了");
});
});
});
const asyncTaskPromise = (message) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`${message} 完了`);
resolve();
}, 1000);
});
};
// 非同期処理の実行
asyncTaskPromise("非同期タスク1")
.then(() => asyncTaskPromise("非同期タスク2"))
.then(() => asyncTaskPromise("非同期タスク3"))
.then(() => {
console.log("すべての非同期タスク完了");
});
ネストが深く可読性が低いコールバック関数に対して、Promiseはよりシンプルに記述することができます。
英語で学ぶ
( In contrast to callback functions, [ which can lead ( to deep nesting and reduced readability, )]) Promise allows ( for a more straightforward and simplified code structure. ) ( ① )
リスニングする
async/awaitで非同期処理を簡潔に
Promiseを利用することで、コールバック関数と比較してシンプルに記述することが可能ですが、async / await を利用すると、より簡潔に直感的に非同期処理を制御することができます。
英語で学ぶ
( By using Promise, ) it (仮主語) becomes possible ( to write code more simply ( compared ( to using callback functions. (真主語) ))) ( ② ) However, ( when using async/await, ) asynchronous operations can be controlled ( in a more concise and intuitive manner. ) ( -③ )
async / await の使い方
関数に 「async」を付与することによって、非同期関数ということを宣言します。
英語で学ぶ
We declare [ that a function is an asynchronous function ( by marking it with “async”. )] ( ① )
async function asyncFunction() {
// 非同期処理を記述
}
await 演算子は、非同期処理が完了するまで待機し、Promise の結果を取得します。
英語で学ぶ
The await operator waits until an asynchronous operation completes and retrieves the result [ of the Promise. ] ( ③ )
async function asyncFunction() {
const response = await fetch('https://example.com/api/data');
const data = await response.json();
console.log(data);
}
Promise と async / await のコード比較
// ユーザーデータを非同期で取得する関数
const getUserData = (userId) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (userId > 0) {
resolve({ id: userId, username: `user${userId}` });
} else {
reject('ユーザーIDが無効です');
}
}, 1000);
});
};
getUserData(1)
.then((userData) => {
console.log('ユーザーデータ取得成功:', userData);
return `${userData.username}のプロフィールを取得しました`;
})
.then((profile) => {
console.log(profile);
return getUserData(2);
})
.then((userData) => {
console.log('ユーザーデータ取得成功:', userData);
return `${userData.username}のプロフィールを取得しました`;
})
.then((profile) => {
console.log(profile);
})
.catch((error) => {
console.error('エラーが発生しました:', error);
});
// ユーザーデータを非同期で取得する関数
const getUserData = (userId) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (userId > 0) {
resolve({ id: userId, username: `user${userId}` });
} else {
reject('ユーザーIDが無効です');
}
}, 1000);
});
};
const fetchUserData = async () => {
try {
const userData1 = await getUserData(1);
console.log('ユーザーデータ取得成功:', userData1);
const profile1 = `${userData1.username}のプロフィールを取得しました`;
console.log(profile1);
const userData2 = await getUserData(2);
console.log('ユーザーデータ取得成功:', userData2);
const profile2 = `${userData2.username}のプロフィールを取得しました`;
console.log(profile2);
} catch (error) {
console.error('エラーが発生しました:', error);
}
};
fetchUserData();
実行結果は以下です。
ユーザーデータ取得成功: {id: 1, username: 'user1'}
user1のプロフィールを取得しました
ユーザーデータ取得成功: {id: 2, username: 'user2'}
user2のプロフィールを取得しました
上記のコードのように、async / await を利用することで、非同期処理をより簡潔に直感的に制御することが可能になります。
英語で学ぶ
( By using async / await, ( as in the code above, ) you can control asynchronous processing ( more concisely and intuitively. ) ( ③ )
リスニングする
この記事で出てきた英語5選
- asynchronously… → 非同期的に
- straightforward… → 〔人が〕率直な、正直な
- allow for… → ~を可能にさせる、~という効果がある
- compare… → 比較する、比べる
- Promise… → 約束する、約束