今回はJavaScriptのPromiseについて、重めの処理をする際に順番を制御するのに利用したので説明します。
データ読込処理などで処理の順番を制御する
データの読込など時間のかかる処理を行う場合を考えてみます。例えば以下は我々の開発しているプロジェクト管理ツールにExcelのデータをコピーして、ペーストでデータを貼り付けるイメージです。
このままだとペーストした場合に、全ての処理が終わってからブラウザ側のDOMに反映されます。処理に時間がかかるり何もされてないように見えるので、良く考えられる対応は処理の完了前後にデータをロードしているスピナーのようなイメージ画像などを表示する対応です。処理の開始時にイメージ画像を表示、表示後にペースト処理、ペースト処理の完了後にイメージ画像を消去するといった流れです。
このようにJavaScriptで処理の完了を待ち、その順番を制御するのにPromiseを利用します。
Promiseについて
Promiseを使った非同期処理の基本構文はPromiseオブジェクトを作成して、then() でコールバックを登録していきます。then() で setTimeout() を実行しても良いですが、今回はコンストラクタで実行します。
// 1:new Promise()によりPromiseオブジェクトを作成 var promise = new Promise((resolve, reject) => { // 3: 非同期処理実行 setTimeout(() => { console.log("対象処理の実行"); var result="処理結果"; // 4: 完了時にresolve()を実行、結果を渡すことも可能 resolve(result); }, 500); }); // 2:Promiseオブジェクトにthen()でコールバックを登録 promise.then((result) => { // 5:resolve() の引数を渡してコールバック実行 console.log(result); });
上記の書き方だと、gulp-uglifyでmin化したときにエラーとなってしまいました。ちょっと調べて解決策がわからなかったのですが、Promiseの引数や then() をfunction形式にして対応することができます。
// 1:new Promise()によりPromiseオブジェクトを作成 var promise = new Promise(function(resolve, reject){ // 3: 非同期処理実行 setTimeout(function(){ console.log("対象処理の実行"); var result="処理結果"; // 4: 完了時にresolve()を実行、結果を渡すことも可能 resolve(result); }, 500); }); // 2:Promiseオブジェクトにthen()でコールバックを登録 promise.then(function(result){ // 5:resolve() の引数を渡してコールバック実行 console.log(result); });
上記を基本構文として説明していきます。まず new Promise() でインスタンス化することでpromiseオブジェクトを作成しています。このpromiseオブジェクトには3つの状態があります。
- pending:初期状態。成功も失敗もしていません。
- fulfilled:処理が成功して完了したことを意味します。コールバック関数内で resolve() が呼ばれます。
- rejected:処理が失敗したことを意味します。コールバック関数内で reject() が呼ばれます。
reject() は、明示的に reject() された場合だけでなく、コンストラクタでエラーがスローされた場合に暗黙的にも発生します。この成功の resolve() や失敗の reject() が呼ばれた場合の処理を then() で定義します。then() は上記の基本構文では1つの引数にしてますが、2つの引数をコールバックとして持つことができ、それぞれが成功と失敗に対応します。
promise.then(function(result) { console.log(result); // resolve() }, function(err) { console.log(err); // reject() });
また then() はPromiseオブジェクトを返すので連鎖させてメソッドチェーンにして記述することもできます。このとき値が返されると、その値を指定して次の then() が呼び出されます。
promise.then(function(result){ // 1回目の処理 var val=1; return val + 2; // returnして次の処理に渡す }).then(function(val){ // 2回目の処理 console.log(val)// 3が表示される });
then() の2つのコールバックの名前をonFulfilled,onRejectedとすると以下のようになります。
promise.then(onFulfilled, onRejected)
このように then() では成功と失敗の処理を同時に登録することができます。 また、エラー処理だけを書きたい場合には catch() を使用して以下のように記述することができます。
promise.catch(onRejected)
このは catch は以下と同じ意味となります。
promise.then(undefined, onRejected)
チェーンにして記述されている場合に reject() されるとどうなるか。失敗のコールバックを持つ then() または同等である catch() まで処理はスキップされます。その失敗のコールバックが正常に終了すれば、続く成功のコールバックを処理します。また then(onFulfilled, onRejected) と指定した場合、onFulfilled または onRejected が呼び出されますが、両方が呼び出されることはありません。
promise .then(func1) // resolve() .then(func2,func3) // func1が成功の場合はfunc2を実行、reject()かfunc1が失敗の場合はfunc3を実行 .catch(func4) // 直前実行のfunc2またはfunc3が失敗の場合のみfunc4を実行 .then(func5) // func2またはfunc3が成功の場合、または直前実行のfunc4が成功の場合はfunc5を実行 ;
今回の対応方法
今回は基本構文を基にコンストラクタで非同期処理を行い、例外処理の中で reject() を明示して対応しました。
promise = new Promise(function(resolve, reject){ //対処処理の開始前にimageを表示する console.log(”スピナー画像の表示”); //非同期処理 setTimeout(function(){ //対象処理 try{ //対象処理の実行 console.log(”対象処理の実行”); }catch(e){ //失敗の場合、reject() を呼び出す reject(e); } //成功の場合、resolve() を呼び出す resolve(); }, 500); }); promise.catch(function(e){ //エラー処理 console.error("エラー:", e.message); }).then(function(){ //成功、失敗の共通処理 console.log(”スピナー画像の非表示”); });
スピナー画像の対応後のイメージです。
IE11の対応
PromiseはIEでは未対応です。polyfillを読み込むことで対応できます。以下のリンクを参考にして、scriptタグを追加して対応しました。
https://www.promisejs.org/
<script src="https://www.promisejs.org/polyfills/promise-7.0.4.min.js"></script>
まとめ
今回はPromiseを利用しての、処理の順番の制御方法の説明でした。
以上