【JavaScript】初心者にもわかるPromiseの使い方
JavaScriptのPromiseとは何なのかを、非同期処理とコールバックを交えて解説します。
またサンプルコードを使って、Promiseを単独およびチェインで実行する方法から、同時に複数の非同期処理を実行する方法まで解説を行います。
最後にエラーハンドリングについても、簡単に説明します。
Promiseとは
PromiseとはJavaScriptにおいて、非同期処理の操作が完了したときに結果を返すものです。
非同期処理とは、ある処理が実行されてから終わるまで待たずに、次に控えている別の処理を行うことです。
なぜこのような仕組みがあるのでしょうか?
JavaScriptはシングルスレッドでしか動かない性質があるため、複数の処理を並列で走らせることができません。
そのため効率的に処理をするために考えられた仕組みが非同期処理と呼ばれるものです。
非同期処理とは
先ほども少し触れましたが、非同期処理とは「ある処理が実行されてから終わるまで待たずに、次に控えている別の処理を行うこと」です。
では終わらない処理の結果は、どう取得するのでしょうか?
Promiseは処理が実行中の処理を監視し、処理が問題なく完了すればresolve、反対に問題があればrejectを呼び出してメッセージを表示します。
この辺りは実際のコードをみて、動きをイメージするほうが理解しやすいでしょう。
コールバックとは
コールバックとは、ある関数へ別の関数を渡すことです。
以下のようなイメージの場合、関数Bがコールバック関数になります。
関数A(関数B、引数) {
//実行内容
}
コールバックは使い勝手がいい反面、使いすぎるとコードが非常に読みにくくなる欠点があります。また関数から関数を呼ぶ処理を繰り返しすぎると、あとで問題が発生したときに調べることが手間になりがちです。これをコールバック関数地獄と言います。
非同期処理のコールバック例
sampleFunction1(function(data1) {
sampleFunction2(function(data2) {
sampleFunction3(function(data3) {
sampleFunction4(function(data4) {
//処理
});
});
});
});
JavaScriptにおける非同期処理のコールバック関数地獄はネストが深くなる上に、エラー処理が相まって、可読性を著しく下げる傾向があります。このコールバック地獄を避けるために考えられたの仕組みが、Promiseです。
コールバック関数の扱いに慣れていないときは、あらかじめ経験豊富なベテランの方に設計時点で問題が無いかを聞いてみるほうが良いでしょう。
Promiseの使い方
それではサンプルコードを使って、Promiseの使い方を解説していきます。
基本的な書き方
Promiseの基本的な書き方は、以下のようになります。
new Promise(function(resolve, reject) {
resolve('成功');
});
new Promise(function(resolve, reject) {
reject('失敗');
});
Promiseはresolveとreject、ふたつの関数を引数に取ります。
resolve:処理が成功したときのメッセージを表示する関数
reject:処理が失敗したときのメッセージを表示する関数
といった形で使われます。
try catchを思い浮かべてもらえれば、イメージが湧きやすいかもしれません。
thenを使ってコールバック処理を実行する
では次にPromiseのthenを使って、コールバック処理を実行してみましょう。
次のように書きます。
var sample = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve();
}, 1000);
});
sample.then(function(value) {
console.log("Promise成功!");
});
console.log("先に出力");
thenを使ってコールバック関数を実行する書き方は、すこし複雑です。
promise.thenを実行すると、処理した結果の"Promise成功!"という文字列がresultに引き渡されます。そのためconsole.logでresultを表示すると、"Promise成功!"という文字がコンソール上に表示されます。
そしてサンプルコードの実行結果に注目してください。console.log(result)のあとにconsole.log(“先に出力”)を実行しているにも関わらず、”先に出力”というメッセージが”Promise成功!”よりも先に出力されています。
これが非同期処理と呼ばれるもので、時間がかかる処理が完了する前に次に控えている別の処理が実行されている様子がわかります。
チェインを使った処理のやり方
次はチェインの使い方を説明します。
チェインを使うことで、複数の処理を連結させることができるようになります。
最初の処理が成功した場合は次の処理に自動的に移る・・・を繰り返すことで、より複雑で高度な処理をコーディングできるようになります。
Promiseの処理を連結させる方法
チェインを使って処理を連結させた例が以下コードになります。
コードの中身は、getNumber関数は渡された数字numを受け取り、numに対して3を2回加算するというものです。
.thenを3回連続で使用している箇所がありますが、これがチェインと呼ばれるものです。このように、Promiseでは複数の処理を連続で処理させることが可能です。
function getNumber(num) {
return new Promise(function(resolve, reject) {
// numが3以上ならnumを返し、3未満なら"Falied!"のメッセージを返す
if (num >= 3) {
setTimeout(function() {
resolve(num);
}, 1000);
} else {
reject("Falied!");
}
});
}
// 今回は3を渡しているので、resolveから3が返ってくる
getNumber(3).then(function(result) {
console.log(result);
//numに3を加算して、getNumberに返している
return result + 3;
}).then(function(result) {
//上と同じ処理の繰り返し。これがチェイン
console.log(result);
return result + 3;
}).then(function(result) {
// 最終結果として、numに6を加算した数を表示
console.log(result);
}, function(err){
// 3未満の場合はrejectが呼び出され、"Falied!"のメッセージが表示される
console.log(err);
});
allを使って複数の非同期処理を同時に行う
allを使うことで、複数の非同期処理を同時に実行することができます。
書き方は以下の通りです。
Promise.all(iterable).then(function(message) {
// 結果を表示する処理
}
またサンプルコードは以下の通りです。
// promise1, 2, 3を別個作成する
var promise1 = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve();
}, 300);
});
var promise2 = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve();
}, 1000);
});
var promise3 = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve();
}, 500);
});
// Promise.allを使って、3つのpromiseを同時に実行している
Promise.all([promise1, promise2, promise3]).then(function() {
console.log("Fourth");
});
console.log("First");
setTimeout(function(){
console.log("Third");
}, 600);
console.log("Second");
allはすべての非同期処理がresolveされたタイミングで結果を返します。
上のサンプルの例ですと、promise1は300ms後, promise3は500ms後に処理されることになっていますが、1000ms後にresolveされるpromise2が完了したタイミングで結果を返します。
ですので上のサンプルコードは
- console.log("First");
- console.log("Second");
- setTimeout(function(){console.log("Third")}, 600);
- Promise.all([promise1, promise2, promise3])~
の順番で結果を返していることがわかります。
catchを使ったエラーハンドリングのやり方
エラーハンドリングを行うために必要である、catchの使い方を説明します。
catchを使うことで、チェインを実行している最中にエラーが発生してもエラーを捕まえることができます。
下のコードでは2つ目のチェインに入った段階でエラーを発生させているので、3を加算した結果である「6」を返す前にエラーメッセージを表示させます。
function getNumber(num) {
return new Promise(function(resolve, reject) {
if (num >= 3) {
setTimeout(function() {
resolve(num);
}, 500);
} else {
reject("Falied!");
}
});
}
// 今回は3を渡しているので、resolveから3が返ってくる
getNumber(3).then(function(result) {
console.log(result);
return result + 3;
}).then(function(result) {
// 2つ目の処理でエラーを発生させる
throw new Error('エラー!失敗しました');
console.log(result);
return result + 3;
}).then(function(result) {
console.log(result);
// catchを使うことで、エラーが発生した時点でエラーメッセージを返す
}).catch(function(e) {
console.log('error: ', e);
});
まとめ
いかがだったでしょうか?
本記事で紹介したPromiseをおさらいすると、以下のようになります。
- Promiseは処理が成功すればresolveを返し、失敗ならrejectを返す
- Promiseを使うと、ネストを深くせずに非同期処理のコールバック関数が書ける
- チェインを使うことで、複数の処理を連続して処理できるようになる
- allはすべての非同期処理が完了した時点で、resolveを返す
- catchを使い、エラーが発生した時点でエラーを返すようにできる
これらの特性から、非同期処理時はPromiseを使うようにすると、コード記述時にミスやエラーが発生する可能性を減らすことができます。