Promise とは
Promise とは、Javascriptでの非同期処理の完了を待ってその処理結果を取得するためのオブジェクトです。いわゆるコールバック地獄に陥らずスマートに非同期処理を掛けるのがメリットとしてあります。
Promise による非同期処理
// Promiseオブジェクトを生成し、非同期処理を実行する
var p = new Promise(function(resolve, reject) {
// 非同期で行いたい処理
try {
// 処理結果を返すのは resolve を実行
// 非同期処理が正常終了したことを伝える
resolve('Success ...');
} catch (error) {
// 処理が失敗したことを伝えるには reject を実行
reject('Error ...');
}
});
p.then(function(value) {
// 非同期処理が成功した場合
console.log(value); // Success ...
}).catch(function(value) {
// 非同期処理が失敗した場合
console.log(value); // Error ...
});
Promise オブジェクトの生成
Promiseオブジェクトは次のような構文を使います。Promiseコンストラクタに非同期に実行したい処理を関数で渡します。
new Promise(function(resolve, reject) { /*ここに非同期処理を記述*/ } );
Promiseオブジェクトを上記構文で生成すると、そのタイミングで引数の関数が非同期的に実行されます。どんなに時間がかかる処理を指定してもすぐに次の処理に進みます。
非同期処理の完了は、非同期処理を指定する関数の引数で渡される resolve
, reject
という関数を実行することで表現できます。
一例として、trycatch でエラーになると reject
関数を実行し、非同期処理の失敗を通知し、成功したときは resolve
関数で成功を通知しています。resolve
, reject
の両関数は引数を指定できます。この引数が非同期処理の処理結果(戻り値)となります。この例では文字列で指定していますが、任意のオブジェクトで好きな値を返せます。
もちろん trycatch を使わなくても、任意の条件で成功、失敗の通知が可能です。
then で成功通知を受け取る
生成された Promise オブジェクトの then
関数を実行することで、非同期処理の成功時(resolve
が実行された時)の通知を受け取ることが可能です。then
関数の引数で通知を受け取った際に実行するコールバック関数を指定します。
// 2秒後に配列[1, 2, 3]を返す非同期処理
var p = new Promise(function(resolve) {
setTimeout(function() { resolve([1, 2, 3]); }, 2000);
});
// 非同期処理の成功時の通知を then で受け取る
p.then(function(value) {
// 非同期処理成功時の処理を記述
// 引数で戻り値を受け取れる
console.log(value);
});
console.log('console end ...');
// console end ...
// [1, 2, 3]
2秒後に処理が完了し、配列を返す非同期処理があったとします。そのような処理を Promise
で実行します。成功を待ってから実行する処理は then
関数に記述します。
then
関数内の処理は非同期処理の成功を待機してから実行されるので、この例だと約2秒後に実行されます。ただしこの間に Javascript の同期処理が止まることはないので、最後に書いたコンソールへの出力処理が先に実行されることになります。
この then
関数はチェーンして数珠つなぎにすることが可能です。非同期処理の後に 処理1, 処理2, 処理3 と処理を続けて実行する場合に次のように記述できます。
new Promise(function(resolve) {
// 非同期処理
setTimeout(function() {
resolve(0);
}, 2000);
})
.then(function(num) {
// 処理1: 非同期処理が完了(成功)したら実行される
num++;
console.log('処理1:', num);
return num;
})
.then(function(num) {
// 処理2: 1が終わったら実行される
return new Promise(function(resolve) {
// 非同期処理
setTimeout(function() {
num++;
console.log('処理2:', num);
resolve(num);
}, 2000);
});
})
.then(function(num) {
// 処理3: 2が終わったら実行される
num++;
console.log('処理3:', num);
});
// 処理1: 1
// 処理2: 2
// 処理3: 3
then
関数が前の処理の実行結果を引数で受け取って順次処理が実行されていることが確認できます。処理2はPromiseオブジェクトを返す非同期処理ですが、続く処理3の then
関数ではその実行を待ってから実行されていることが確認できます。
then
関数を使うことでコールバック関数をたくさん使った書き方をせずともすっきりと書くことができました。
catch で処理失敗時の通知を受け取る
非同期処理は常に成功するわけではありません。例えばWebAPIを実行する例を考えると、通信エラーやサーバー内部でのエラーなど様々な要因で成功時の処理とは別に、処理を分岐させなければならないことが多いです。
Promiseオブジェクトの catch
関数は処理失敗時(reject
が実行された時)の通知を受け取ることが可能です。使い方は then
と同じような感じです。
new Promise(function(resolve, reject) {
// 非同期処理
reject('非同期処理が失敗しました。');
})
.then(function(value) {
console.log('Success:', value);
})
.catch(function(value) {
// 非同期処理失敗時
console.log('Error:', value);
});
// Error: 非同期処理が失敗しました。
このコードでは、Promise内の非同期処理で reject
を実行することで、失敗を通知しています。処理が失敗しているので then
関数は実行されず、catch
関数内の処理に飛びます。
then
内でエラーが発生してスローされても catch
に飛んできます。つまり、catch
前のどこかでエラーが発生すると通知が飛んでくることになります。
Promise.all で複数の非同期処理の完了通知を受け取る
ここまで見てきた例は、1つの非同期処理を待機して同期処理とうまく組み合わせる基本的な使い方です。例えば複数の非同期処理を並列に実行し、そのすべてが完了するまで待機するということも Promise.all
関数を使うことで可能です。
例えばそれぞれ 2秒, 3秒 で終わる処理(A, B)があったとします。Aの処理を待ってからBの処理を実行するというのはすでに then
関数を使って実現できる例を示しましたが、今回はABを並列に実行し、両方の実行が完了した際に処理を実行する、という例です。
// 非同期処理A
var a = new Promise((resolve) => {
setTimeout(() => {
resolve('A');
}, 2000);
});
// 非同期処理B
var b = new Promise((resolve) => {
setTimeout(() => {
resolve('B');
}, 3000);
});
// AB両方の完了を待機する
Promise.all([a, b]).then((results) => {
console.log(results) // ['A', 'B']
}).catch((error) => {
// いずれかの非同期処理でエラーが発生すると
// その時点で飛んでくる
});
複数のPromiseオブジェクトを配列にして Promise.all
関数に引数で渡します。Promise.all
関数は、そのすべての実行が成功するのを待って、then
関数に通知を飛ばしてくれます。いずれかの処理でエラーが発生すると catch
に通知が飛びます。
アロー関数の表記にしてみましたが同じ意味です。
Promise.race で最初の通知のみを受け取る
Promise.race
は面白い機能で、複数のPromiseすべての完了を待機するのではなく、一番早くに完了したPromiseの結果を優先します。つまり、最初に完了した処理が成功していれば then
関数が実行され、あるいは最初の完了が失敗の場合は catch
関数が実行されます。
以下の例では複数の非同期処理を実行していますが、1番最初に完了するのは p2
ですので、これが完了したタイミングで then
関数が実行されることが確認できます。p3
については reject
されているのでエラーですが、最初の通知が優先されるため、catch
には飛びません。
// 2秒後に成功
var p1 = new Promise((resolve) => {
setTimeout(() => {
resolve(1);
}, 2000);
});
// 0.1秒後に成功
var p2 = new Promise((resolve) => {
setTimeout(() => {
resolve(2);
}, 100);
});
// 5秒後に失敗
var p3 = new Promise((resolve, reject) => {
setTimeout(() => {
// 失敗しても成功先勝ちになる
reject(3);
}, 5000);
})
// AB両方の完了を待機する
Promise.race([p1, p2, p3]).then((value) => {
// 複数のPromiseで1番速い通知が成功の場合、その結果
console.log(value); // 2
}).catch((error) => {
// 複数のPromiseで1番速い通知が失敗の場合、その結果
});
まとめ
Promise
は様々なライブラリ等で利用されているため、使う機会も多いのでしっかりマスターしたいところです。また Promise
をベースにさらに発展形の asynct
, await
もあるので、こっちも知っておくとよいでしょう。
以上。
コメントを書く