JavaScriptで非同期処理を実装する際に使用すると便利なのが、Promiseオブジェクトです。
Promiseを使用することにより、コールバック関数を使って記述するよりも簡潔にコードをまとめることができます。
今回は、Promiseを使用して非同期処理を実装する方法について、解説していきたいと思います。
Promiseとは?
Promiseは、JavaScriptで生成可能なオブジェクトの一種です。
非同期処理の実装時に使用されるオブジェクトで、引数に渡した関数の処理が完了した際に、処理が成功したか否かを結果として受け取ることができます。
また、Promiseオブジェクトにはいくつかのメソッドが用意されており、それぞれ対応するメソッドを使用することで、取得結果に応じて実行する処理を指定することもできます。
同期処理と非同期処理
プログラミングは基本的に、上から下へと記述した順にコードを実行し、記述した内容の処理が全て完了してから次の行の処理へと移行します。
このような処理の流れを、同期処理と呼びます。
一方で、その行に書かれた処理内容が完了するのを待つことなく、次の行へと処理を移行させるケースも中には存在します。
こうした処理は反対に、非同期処理と呼ばれています。
非同期処理は、主にデータ通信などの時間がかかりやすい処理を実行する際に使用する処理方法です。
同期処理の場合、特定の処理に時間がかかると必然的にプログラム全体の処理も止まってしまうことになりますが、非同期処理であればプログラムの進行を止めることなく処理を実行することができます。
JavaScriptにおける「コールバック地獄」
非同期処理を実行する際によく陥りやすいのが、「コールバック地獄」 と呼ばれる現象です。
ほとんどの非同期処理では、コールバック関数が使用されます。
コールバック関数とは、関数やメソッドに引数として渡し、渡した先のブロック内で呼び出される関数のことを言います。
これにより、例えば通信処理で受け取った内容を画面に描写するといったような、特定の処理が完了した後に実行する処理を指定することができるのです。
ですが、コールバック関数は使用する際のデメリットとして、コードが複雑化しやすいという難点があります。
以下の例を見てみましょう。
【サンプルコード】
function sampleFunc(num, callback) {
setTimeout(() => {
callback(num * 10);
}, Math.random() * 1000);
}
function sampleCallback() {
sampleFunc(5, (result) => {
console.log(result);
sampleFunc(result, (result) => {
console.log(result);
sampleFunc(result, (result) => {
console.log(result);
});
});
});
}コードとしては、sampleFuncという非同期処理を行う関数を定義し、引数のコールバック関数内で連続して呼び出すだけという簡単なものです。
ところが、視覚的にコードを見てみると、簡単な内容であるにも関わらず瞬時に理解するのが難しいコードになってしまっています。
このように、コールバック関数のネストが深くなることで可読性が下がってしまう現象のことを、コールバック地獄と呼びます。
Promiseオブジェクトは、このコールバック地獄を解消するための記述方法の1つとして活用されています。
Promiseの使い方
それでは、Promiseオブジェクトの実際の使い方について、順に確認していきましょう。
基本構文
Promiseオブジェクトを使用する際は、以下の形式でインスタンスを生成します。
【基本構文】
const samplePromise = new Promise((resolve, reject) => {
//処理内容;
});Promiseオブジェクトには、「PromiseStatus」という処理状態を表すステータスがあり、以下の3つのうちのいずれかで関数の処理状態を表します。
- pending: 未解決(処理中)
- fulfilled: 解決済み(処理が成功した状態)
- rejected: 拒否(処理が失敗した状態)
インスタンス生成時の状態は pendingです。
コンストラクタに渡す関数の引数には、resolveと reject の2つの関数を取ることができ、resolve を実行することで fulfilledに、rejectの場合は rejected に状態を移行することができます。
【サンプルコード】
const samplePromise = new Promise((resolve, reject) => {
if (Math.random() < 0.5) {
// fulfilledに移行
resolve();
} else {
// resolvedに移行
reject(");
}
});Promise.then() を使ってコールバック処理を記述する
Promiseオブジェクトを使用して、通常の非同期処理のコールバック関数と同じ動作をさせたい場合には、thenメソッドを使用します。
thenメソッドは、対象のPromiseオブジェクトの状態が fulfilled、もしくは rejectedに移行した段階で、指定の処理を実行させることができるメソッドです。
メソッドを使用する際は、以下のような形式で記述します。
【サンプルコード】
samplePromise.then(
function(data) {
// Promiseオブジェクトが fulfilled の場合の処理;
},
function(error) {
// Promiseオブジェクトが rejected の場合の処理;
},
);thenメソッドでは、2つの引数を取ることができます。
第1引数には、Promiseオブジェクトの状態が fulfilledの場合に実行する関数を指定します。
反対に、第2引数にはオブジェクトの状態が rejectedの場合に実行する関数を指定します。
また、resolve関数 や reject関数の実行時に値を渡すことで、それぞれの関数の引数として受け取ることができます。
以下の例を見てみましょう。
【サンプルコード】
function samplePromise(){
return new Promise(function(resolve, reject){
const num = Math.random();
if (num < 0.5) {
resolve(num);
} else {
reject(num);
}
});
}
samplePromise()
.then(
function(data) {
console.log("success:", data);
},
function(error) {
console.log("error:", error);
},
);【実行結果】
success: 0.4786429022064871サンプルコードを実際に動かしてみると、Promiseオブジェクトの関数内で生成された numの値が thenメソッドに渡されているのが分かると思います。
このように、各関数の引数を活用することで、処理結果を Promiseから thenメソッドへと引き渡すことができます。
Promise.catch() を使ってエラー処理を記述する
先ほども解説したように、Promiseオブジェクトの関数が失敗(rejected)だった場合に実行する処理は、thenメソッドの第2引数で指定することができます。
ですがその他にも、失敗した際に実行する処理を指定する方法があります。
それが、catchメソッドを使用する方法です。
【サンプルコード】
samplePromise()
.then(
function(data) {
console.log("success:", data);
return data;
}
)
.catch(
function(error) {
console.log("error:", error);
}
);catchメソッドは、reject関数の実行時だけでなく、エラー発生時や throwを投げた際にも例外処理を行うことができます。
【サンプルコード】
function samplePromise() {
return new Promise(function() {
throw new Error("エラーが発生しました");
});
}
samplePromise()
.then(
function(data) {
console.log("success:", data);
return data;
}
)
.catch(
function(error) {
console.log(error);
}
);【実行結果】
Error: エラーが発生しました
at /tmp/main.js:3:15
at new Promise (<anonymous>)
at samplePromise (/tmp/main.js:2:12)
at Object.<anonymous> (/tmp/main.js:7:1)
at Module._compile (node:internal/modules/cjs/loader:1376:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
at Module.load (node:internal/modules/cjs/loader:1207:32)
at Module._load (node:internal/modules/cjs/loader:1023:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:12)
at node:internal/main/run_main_module:28:49catchメソッドと同様の動きを thenメソッドで実装することは不可能ではありませんが、メソッドを使い分けることによりコードの意図が視覚的にも明確になります。
基本的には、成功処理は thenメソッド、失敗処理は catchメソッドで記述するようにするといいでしょう。
Promiseをチェーンして処理を連結する
Promiseオブジェクトを使用する際は、複数の非同期処理を同期的に繋げて処理したり、すべての処理を並行して実行し終えた後に特定の処理を行わせることもできます。
それぞれの方法について見ていきましょう。
Promise処理を連結する
thenメソッドや catchメソッドは、Promiseオブジェクトを戻り値として返すため、連続して実行することができます。
【サンプルコード】
function samplePromise() {
return new Promise(resolve => resolve(Math.floor(Math.random() * 10)));
}
samplePromise()
.then(
function(data) {
console.log("success1:", data);
return data * 10;
}
)
.then(
function(data) {
console.log("success2:", data);
return data * 10;
}
)
.then(
function(data) {
console.log("success3:", data);
}
);【実行結果】
success1: 5
success2: 50
success3: 500メソッドを連続して繋げることにより、複数の非同期処理を同期的に実行することも可能です。
上記コードのように、メソッド内で指定した戻り値が Promiseオブジェクト以外のデータ型である場合も、Promiseオブジェクトが保持する値として内包された上でオブジェクトが返却されるため、連続してメソッドを実行することができます。
Promise.all() で複数のPromiseを連結する
1つの非同期処理ではなく、複数の非同期処理がすべて完了したタイミングで特定の処理を行いたい場合もあると思います。
先ほどの例はあくまで非同期処理を順に行う方法のため、すべての処理が完了するまでには時間がかかってしまいます。
上記のような処理を実装したい場合は、thenメソッドをチェーンする方法ではなく、Promise.allメソッドを活用して実装しましょう。
【サンプルコード】
function samplePromise(time) {
return new Promise(resolve => resolve(time + "秒"), time * 1000);
}
Promise.all([
samplePromise(1),
samplePromise(3),
samplePromise(5)
])
.then(function(data) {
console.log(data);
console.log('すべての処理が完了しました');
});【実行結果】
[ '1秒', '3秒', '5秒' ]
すべての処理が完了しましたPromise.allメソッドを使用する際は、実行対象とする Promiseオブジェクトを要素として格納した配列を引数に渡します。
thenメソッドを繋げて記述することで、格納した Promiseオブジェクトの処理がすべて完了した段階で指定の処理が実行されるようになります。
Promise.allメソッドで処理を実行した場合、resolve関数などに渡した値は上記の結果のように、配列として全ての値をまとめた上で引き渡されます。
PromiseでAjax通信を実装する
Promiseオブジェクトは、Ajax通信を行う際に使用される Fetch APIとも密接に関係しています。
簡単な例を元に、実際の使い方を見てみましょう。
fetch()の結果をPromiseで受け取る
Fetch APIでサーバーリクエストやレスポンスの受け取りをする際に使用するのが、fetchメソッドです。
fetchメソッドは、戻り値でPromiseオブジェクトを返すため、コールバック関数の代わりに thenメソッドや catchメソッドでレスポンス処理を記述することができます。
【サンプルコード】
fetch("https://example.com")
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error("通信エラー:", error));Promiseをベースとしているので、thenメソッドや catchメソッドだけでなく、async/await も使用することができます。
【サンプルコード】
async function getData() {
try {
const response = await fetch("https://example.com");
const data = await response.json();
console.log(data);
} catch (error) {
console.error("通信エラー:", error);
}
}より簡単にサーバー通信を行う方法として、Fetch API を使用して非同期処理を行うケースが多くあるので、合わせて覚えておくといい技術です。
Promiseを使用する際のエラーハンドリングや例外処理
Promiseオブジェクトで例外処理を実装する方法は、既に紹介した catchメソッドの他に、tryメソッドを使用する方法があります。
詳しい使い方について見ていきましょう。
Promise.try()でtry-catch例外処理を実装する
tryメソッドは、ES2025で追加された新しい機能の1つです。
従来のJavaScriptでは、同期処理と非同期処理の仕様の違いにより、それぞれ例外処理の記述方法を変える必要がありました。
一方、このtryメソッドでは、同期・非同期によって処理を変えることなく、一律にPromiseオブジェクトをベースとした処理を実装することができます。
tryメソッドが特に有効的なのは、同期処理と非同期処理が同じブロック内に存在するケースです。
これまでの場合は、Promiseオブジェクトを返すか否かで条件分岐を行う、もしくは同期・非同期に関わらず Promiseオブジェクトで戻り値を返すなどして対処をしていました。
【サンプルコード】
try {
const result = sampleFunc();
if (result instanceof Promise) {
result.catch(error => console.error(‘非同期エラー:', error));
} else {
console.log(‘処理結果:', result);
}
} catch (error) {
console.error('同期エラー:', error);
}上記のような方法の場合、必然的にコードが複雑化してしまうため、可読性や監理のしやすさが下がる原因となってしまいます。
tryメソッドであれば、これらの問題を解決することができます。
【サンプルコード】
const syncFunc = () => {
return "同期処理";
};
const asyncFunc = async () => {
return "非同期処理";
};
Promise.try(syncFunc).then(console.log);
Promise.try(asyncFunc).then(console.log); 【実行結果】
同期処理
非同期処理処理内容の違いを特に意識することなく、通常通りに関数を記述していますが、どちらも問題なく thenメソッドで処理を繋ぐことができています。
このように、同期・非同期に関わらず Promiseオブジェクトを受け取ることができるので、処理内容を統一することができ、コードを簡潔に見やすく記述することができます。
ただし、先ほど紹介したように、tryメソッドは ES2025で追加された新しいメソッドです。
そのため、環境によってはメソッドが活用できないケースもあるため、注意が必要です。
まとめ
いかがでしたか?今回は、Promiseを使った非同期処理の実装方法について解説しました。
非同期処理は、サーバーとの通信処理などでよく活用される技術ですので、使い方をしっかりと理解しておくようにしましょう。
JavaScriptの勉強方法は?
書籍やインターネットで学習する方法があります。昨今では、YouTubeなどの動画サイトやエンジニアのコミュニティサイトなども充実していて多くの情報が手に入ります。
そして、より効率的に知識・スキルを習得するには、知識をつけながら実際に手を動かしてみるなど、インプットとアウトプットを繰り返していくことが重要です。特に独学の場合は、有識者に質問ができたりフィードバックをもらえるような環境があると、理解度が深まるでしょう。
ただ、JavaScriptに限らず、ITスキルを身につける際、どうしても課題にぶつかってしまうことはありますよね。特に独学だと、わからない部分をプロに質問できる機会を確保しにくく、モチベーションが続きにくいという側面があります。独学でモチベーションを維持する自信がない人にはプログラミングスクールという手もあります。費用は掛かりますが、その分スキルを身につけやすいです。しっかりと知識・スキルを習得して実践に活かしたいという人はプログラミングスクールがおすすめです。
プログラミングスクールならテックマニアがおすすめ!
ITスキル需要の高まりとともにプログラミングスクールも増えました。しかし、どのスクールに通うべきか迷ってしまう人もいるでしょう。そんな方にはテックマニアをおすすめします!これまで多くのITエンジニアを育成・輩出してきたテックマニアでもプログラミングスクールを開講しています。
<テックマニアの特徴>
・たしかな育成実績と親身な教育 ~セカンドキャリアを全力支援~
・講師が現役エンジニア ~“本当”の開発ノウハウを直に学べる~
・専属講師が学習を徹底サポート ~「わからない」を徹底解決~
・実務ベースでスキルを習得 ~実践的な凝縮カリキュラム~
このような特徴を持つテックマニアはITエンジニアのスタートラインとして最適です。
話を聞きたい・詳しく知りたいという方はこちらからお気軽にお問い合わせください。