JavaScript 再入門(その21) 非同期処理 2 プロミスの動作

プロミスの動作

前回の記事でプロミスの大まかな動きを見てみました。

JavaScript 再入門(その20) 非同期処理 1 非同期処理とPromise
非同期処理とPromise 非同期処理とは? まずは、通常の処理方法、同期処理から説明します。通常のプログラムは上から順番にコードを処理して、処理が終わると次の…
sakura-system.com

プロミスって何をしてるの?

今回は、プロミスの状態や値の受け渡しなどの、プロミスの動作について説明します。

まずは、Promise を定義後の状態をみてみましょう。
このスクリプトの Promise は非同期処理ですが、処理は何もしていないので実務上役に立つスクリプトではありませんが、Promise の動作を確認するのには非常にシンプルで理解しやすいスクリプトですので、我慢してお付き合いください。

var promise1 ;
promise1 = new Promise( ( resolve , reject ) =>{
})
console.log( promise1 ) ;
/*
Promise {<pending>}
	[[Prototype]]: Promise
	[[PromiseState]]: "pending"
	[[PromiseResult]]: undefined
*/

promise1 は宣言されただけで完了も失敗もしていないので待機 (pending) 状態です。

Promise の内容は非常にシンプルで、プロミスの状態を表す PromiseState と、プロミスが渡す値 PromiseResult を持っています。

プロミスの状態 (PromiseState) と、プロミスの値 (PromiseResult)

プロミスの状態 (PromiseState) は以下のいずれかとなります。

  • 待機 (pending): 初期状態。成功も失敗もしていません。
  • 完了 (fulfilled): 処理が成功して完了したことを意味します。
  • 失敗 (rejected): 処理が失敗したことを意味します。

promise1 を resolve() してみます。

var promise1 ;
promise1 = new Promise( ( resolve , reject ) =>{
	resolve() ;
})
console.log(promise1) ;
/*
Promise {<fulfilled>: undefined}
	[[Prototype]]: Promise
	[[PromiseState]]: "fulfilled"
	[[PromiseResult]]: undefined
*/

プロミスの状態 ( PromiseState ) が “fulfilled” へと変わりました。

resolve() に値を設定してみます。

var promise1 ;
promise1 = new Promise( ( resolve , reject ) =>{
	resolve( '完了しました!' ) ;
})
console.log(promise1) ;
/*
Promise {<fulfilled>: '完了しました!'}
	[[Prototype]]: Promise
	[[PromiseState]]: "fulfilled"
	[[PromiseResult]]: "完了しました!"
*/

プロミスの状態 (PromiseState) が “fulfilled” へ変わり、同時にプロミスの値 (PromiseResult) に resolve() で設定した値 “完了しました!” が設定されています。

次に promise1 を reject() してみます。

var promise1 ;
promise1 = new Promise( ( resolve , reject ) =>{
	reject( '失敗しました!' ) ;
})
console.log(promise1) ;
/*
Promise {<rejected>: '失敗しました!'}
	[[Prototype]]: Promise
	[[PromiseState]]: "rejected"
	[[PromiseResult]]: "失敗しました!"

Uncaught (in promise) 失敗しました! <--- エラーが throw される。
*/

プロミスの状態 (PromiseState) が “rejected” へ変わり、同時にプロミスの値 (PromiseResult) に reject() で設定した値 “失敗しました!” が設定されています。そして、エラーが throw されます。
reject() で投げられたエラーは .catch() メソッドで処理することができます。

var promise1 ;
promise1 = new Promise( ( resolve , reject ) =>{
	reject( '失敗しました!' ) ;
}).catch( ( e ) => { console.log( 'エラー発生:', e ) ; })
console.log(promise1) ;
/*
Promise {<rejected>: '失敗しました!'}
	[[Prototype]]: Promise
	[[PromiseState]]: "rejected"
	[[PromiseResult]]: "失敗しました!"

エラー発生: 失敗しました!
*/

お気づきになりましたか?
resolve() や reject() で渡された値は、.then() や .catch() メソッドの引数となり関数で利用することができます。

var promise1 ;
promise1 = new Promise( ( resolve , reject ) =>{
	resolve( '成功しました!' ) ;
}).then( ( res ) => { console.log( '正常終了:', res ) ;
}).catch( ( e ) => { console.log( 'エラー発生:', e ) ;
})
console.log(promise1) ;
/*
Promise {<pending>}
	[[Prototype]]: Promise
	[[PromiseState]]: "fulfilled"
	[[PromiseResult]]: undefined

正常終了: 成功しました!
*/

プロミスチェーンの値の受け渡し

プロミスをチェーンにして、チェーン間でどの様に値が渡されているかを見てみましょう。

var promise1 ;
promise1 = new Promise( ( resolve , reject ) =>{
	resolve( '<1> 成功しました!' ) ;					// <--- resolve()(PromiseResult)に値を設定
}).then( ( res ) => {									// <--- resolve値(PromiseResult)が引数となり関数へ渡される
	console.log( '(1) 正常終了:', res ) ;				// (1) 正常終了: <1> 成功しました!
	return new Promise( ( resolve , reject ) =>{		// <--- プロミスをreturn
		resolve( '<2> 成功しました!' ) ;				// <--- resolve()(PromiseResult)に値を設定
	}) ;
}).then( ( res ) => {									// <--- 前のthen()でreturnされたresolve値(PromiseResult)が引数となり関数へ渡される
	console.log( '(2) 正常終了:', res ) ;				// (2) 正常終了: <2> 成功しました!
	return '<3> すべての処理が正常終了しました。' ;		// <--- 文字列をreturn
}).then( ( res ) => {									// <--- 前のthen()でreturnされた文字列が引数となり関数へ渡される
	console.log( '(3) 正常終了:', res ) ;				// (3) 正常終了: <3> すべての処理が正常終了しました。
}).catch( ( e ) => { console.log( 'エラー発生:', e ) ;
})
/*
(1) 正常終了: <1> 成功しました!
(2) 正常終了: <2> 成功しました!
(3) 正常終了: <3> すべての処理が正常終了しました。
*/

.then() メソッドでは、return で渡された値が、次の then() メソッドの引数となり関数に渡されます。
return で渡されるるものがプロミス型であれば、プロミス値 (PromiseResult) が引数に渡されます。

.catch()

2つ目のプロミスを reject() で失敗させてみましょう。

var promise1 ;
promise1 = new Promise( ( resolve , reject ) =>{
	resolve( '<1> 成功しました!' ) ;					// <--- resolve()に値(PromiseResult)を設定
}).then( ( res ) => {									// <--- resolve値(PromiseResult)が引数となり関数へ渡される
	console.log( '(1) 正常終了:', res ) ;				// (1) 正常終了: <1> 成功しました!
	return new Promise( ( resolve , reject ) =>{		// <--- プロミスをreturn
		reject( '<2> 失敗しました!' ) ;					// <--- reject()に値(PromiseResult)を設定
	}) ;
}).then( ( res ) => {									// <--- 前のthen()メソッドでrejectされたので飛ばされる
	console.log( '(2) 正常終了:', res ) ;				
	return '<3> すべての処理が正常終了しました。' ;		
}).then( ( res ) => {									// <--- 前のthen()メソッドでrejectされたので飛ばされる
	console.log( '(3) 正常終了:', res ) ;				
}).catch( ( e ) => { console.log( 'エラー発生:', e ) ;	// エラー発生: <2> 失敗しました!
})
/*
(1) 正常終了: <1> 成功しました!
エラー発生: <2> 失敗しました!
*/

失敗 (reject) が発生した以降の .then() メソッドが無視されて、.catch() メソッドがエラーを受け取り、失敗時の処理が実行されました。

.finally()

.finally() メソッドで完了時も失敗時も常に実行される処理を追加することもできます。

var promise1 ;
promise1 = new Promise( ( resolve , reject ) =>{
	resolve( '<1> 成功しました!' ) ;					// <--- resolve()に値(PromiseResult)を設定
}).then( ( res ) => {									// <--- resolve値(PromiseResult)が関数へ渡される
	console.log( '(1) 正常終了:', res ) ;				// (1) 正常終了: <1> 成功しました!
	return new Promise( ( resolve , reject ) =>{		// <--- プロミスをreturn
		reject( '<2> 失敗しました!' ) ;					// <--- reject()に値(PromiseResult)を設定
	}) ;
}).then( ( res ) => {									// <--- 前のthen()でrejectされたので飛ばされる
	console.log( '(2) 正常終了:', res ) ;				
	return '<3> すべての処理が正常終了しました。' ;		
}).then( ( res ) => {									// <--- 前のthen()でrejectされたので飛ばされる
	console.log( '(3) 正常終了:', res ) ;				
}).catch( ( e ) => { console.log( 'エラー発生:', e ) ;	// エラー発生: <2> 失敗しました!
}).finally( () => { console.log( '終了処理' ) ;			// <--- 完了時も失敗時も実行されます
})
/*
(1) 正常終了: <1> 成功しました!
エラー発生: <2> 失敗しました!
終了処理
*/

コールバック地獄からの脱出

では、前記事の非同期処理でプロフィールを取得するスクリプトに、エラー処理と終了処理を入れてみましょう。

let profile = '' ;	// プロフィール
let dataSet = {		// プロフィールデータ
	carName : 'AE86 SPRINTER TRUENO GT-APEX 3door ' , // 車名
	firstName : 'Takumi ' ,		// 名前
	lastName : 'Fujiwara ' ,	// 苗字
}
// 非同期でリクエストに対するデータをX秒後に返す関数
let GetReqData = function( argReq ) {
	return new Promise( ( resolve , reject ) => {	// 非同期処理の結果を Promise で返します
		setTimeout(
			() => {
				if ( argReq in dataSet ) {			// リクエストがプロフィールデータに存在するか
					resolve( dataSet[ argReq ] ) ;	// リクエストが正しければ正常終了して値も返すよ! 
				} else {
					reject( argReq ) ;				// リクエストが正しくなければリクエストをエラーとして投げる
				}
			} , 
			Math.random() * 1000
		) ;
	}) ;
}

// コールバック関数(プロフィールの編集)
let editProfile = ( argProfData ) => {
	profile += argProfData ;				// プロフィールに返された値を付加する。
}
//
let car = new GetReqData( 'carName' ) ;		// 車名取得インスタンス生成
let lname = new GetReqData( 'lastName' ) ;	// 苗字取得インスタンス生成
let fname = new GetReqData( 'firstName' ) ;	// 名前取得インスタンス生成
// 非同期で 車名+苗字+名前 のプロフィールを取得したい
car.then( ( argRes ) => {					// 車名の取得と車名の取得が成功(resolve)したときの処理
	editProfile( argRes ) ;					// 車名をプロフィールに編集
	return lname ;							// 苗字の取得 return によって結果は次の then() 関数の引数になる
}).then( ( argRes ) => {					// 苗字の取得が成功(resolve)したときの処理
	editProfile( argRes ) ;					// 苗字をプロフィールに編集
	return fname ;							// 名前の取得 return によって結果は次の then() 関数の引数になる
}).then( ( argRes ) => {					// 名前の取得が成功(resolve)したときの処理
	editProfile( argRes ) ;					// 名前をプロフィールに編集
}).catch( ( e ) => {						// 失敗時の処理
	console.error( 'Request ' + e + ' is not found !' ) ;
}).finally( () => {							// 終了処理
	console.log( 'Profile:' + profile ) ;	// プロフィールを表示
}) ;
console.log( '<TOYOTA>') ; // 一番最初に表示される。
/*
<TOYOTA>
Profile:AE86 SPRINTER TRUENO GT-APEX 3door Fujiwara Takumi
*/

エラー処理と終了処理を入れてもスッキリとしたスクリプトですね。
コールバック地獄から見事脱出できました。

参考リンク

MDN 開発者向けのウェブ技術 > 標準組み込みオブジェクト > Promise
MDN 開発者向けのウェブ技術 > プロミスの使用
MDN 開発者向けのウェブ技術 > JavaScript「再」入門