JavaScript 再入門(その22) 非同期処理 3 Promise の待ち合わせメソッド

Promise の待ち合わせメソッド

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

JavaScript 再入門(その20) 非同期処理 1 非同期処理とPromise
非同期処理とPromise 非同期処理とは? まずは、通常の処理方法、同期処理から説明します。通常のプログラムは上から順番にコードを処理して、処理が終わると次の…
sakura-system.com
JavaScript 再入門(その21) 非同期処理 2 プロミスの動作
プロミスの動作 前回の記事でプロミスの大まかな動きを見てみました。 プロミスって何をしてるの? 今回は、プロミスの状態や値の受け渡しなどの、プロミスの動作につい…
sakura-system.com

プロミスの待ち合わせメソッド

プロミスには、複数のプロミスの状態により .then() .catch() .finally() メソッドを実行するメソッドがあります。

説明するにあたって、プロミスを返すコンストラクタ関数を定義します。
以下の例題はこのコンストラクタ関数からインスタンスを作成します。

関数は処理開始時に “Start: 第1引数” を表示し、延滞後第1引数を返します。
第1引数を返す際に、第2引数が真:(true) であれば完了:(resolve) で返し、第2引数が偽:(false) であれば失敗:(reject) で返します。

// 非同期でX秒後にメッセージを返す関数
let Promisef = function( argVal , argSuccess ) {
	return new Promise( ( resolve , reject ) => {
		console.log( 'Start:' , argVal ) ;
		setTimeout(
			() => {
				if ( argSuccess ) {
					resolve( 'o:' + argVal ) ;
				} else {
					reject( 'x:' + argVal ) ;
				}
			} ,
			Math.random() * 1000 
		) ;
	}) ;
}

Promise.all()

Promise.all() は配列で指定されたプロミスの全てが完了した時点で .then() メソッドを実行します。
指定されたプロミス内で失敗があれば、失敗を検出した時点で .catch() メソッドを実行します。

Promise.all() メソッドから .then() メソッドに渡される値は、各プロミスの実行結果が、Promise.all() メソッドに指定された配列順に配列型で返されます。

let promiseA , promiseB , promiseC ;
promiseA = new Promisef( 'A' , true ) ;
promiseB = new Promisef( 'B' , true ) ;
promiseC = new Promisef( 'C' , true ) ;
//
Promise.all( [ promiseA , promiseB , promiseC ] )
	.then( ( argRet ) => { console.log( '<All finished normally>' , ...argRet) ; } )
	.catch( ( argRet ) => { console.log( '<All caught error>' , ...argRet) ; } )
	.finally( () => { console.log( '<All finally>') ; } ) ;
//
promiseA
	.then( ( argRet ) => { console.log( argRet , 'is resolve' ) ; } )
	.catch ( ( argRet ) => { console.log( argRet , 'is reject' ) ; } );
promiseB
	.then( ( argRet ) => { console.log( argRet , 'is resolve' ) ; } )
	.catch ( ( argRet ) => { console.log( argRet , 'is reject' ) ; } );
promiseC
	.then( ( argRet ) => { console.log( argRet , 'is resolve' ) ; } )
	.catch ( ( argRet ) => { console.log( argRet , 'is reject' ) ; } );
/*
Start: A
Start: B
Start: C
o:B is resolve
o:C is resolve
o:A is resolve
<All finished normally> o:A o:B o:C
<All finally>
*/

promiseB の第2引数を false にして、reject が返るようにします。

let promiseA , promiseB , promiseC ;
promiseA = new Promisef( 'A' , true ) ;
promiseB = new Promisef( 'B' , false ) ;	// <--- 失敗になります。
promiseC = new Promisef( 'C' , true ) ;
//
Promise.all( [ promiseA , promiseB , promiseC ] )	// Promise.all()
	.then( ( argRet ) => { console.log( '<All finished normally>' , ...argRet) ; } )
	.catch( ( argRet ) => { console.log( '<All caught error>' , ...argRet) ; } )
	.finally( () => { console.log( '<All finally>') ; } ) ;
//
promiseA
	.then( ( argRet ) => { console.log( argRet , 'is resolve' ) ; } )
	.catch ( ( argRet ) => { console.log( argRet , 'is reject' ) ; } );
promiseB
	.then( ( argRet ) => { console.log( argRet , 'is resolve' ) ; } )
	.catch ( ( argRet ) => { console.log( argRet , 'is reject' ) ; } );
promiseC
	.then( ( argRet ) => { console.log( argRet , 'is resolve' ) ; } )
	.catch ( ( argRet ) => { console.log( argRet , 'is reject' ) ; } );
/*
Start: A
Start: B
Start: C
o:A is resolve
x:B is reject
<All caught error> x : B <--- reject した時点でPromise.allへコールバックしています。
<All finally>
o:C is resolve
*/

失敗が検出された時点で .catch() メソッドへ失敗状態とエラーが throw されました。
この場合、Promise.all() は、promiseC の実行結果を待たずに終了します。

Promise.allSettled()

Promise.allSettled() は配列で指定されたプロミスの全てが完了または失敗した時点で .then() メソッドを実行します。
指定されたプロミスで失敗を検出しても .catch() メソッドは実行されません。

Promise.allSettled() メソッドから .then() メソッドに渡される値は、各プロミスの実行状態と結果が、Promise.allSettled() メソッドに指定された配列順に配列型で返されます。

let promiseA , promiseB , promiseC ;
promiseA = new Promisef( 'A' , true ) ;
promiseB = new Promisef( 'B' , false ) ;	// <--- 失敗になります。
promiseC = new Promisef( 'C' , true ) ;
//
Promise.allSettled( [ promiseA , promiseB , promiseC ] )	// Promise.allSettled
	.then( ( argRet ) => { console.log( '<allSettled finished normally>' , argRet) ; } )
	.catch( ( argRet ) => { console.log( '<allSettled caught error>' , argRet) ; } )
	.finally( () => { console.log( '<allSettled finally>') ; } ) ;
//
promiseA
	.then( ( argRet ) => { console.log( argRet , 'is resolve' ) ; } )
	.catch ( ( argRet ) => { console.log( argRet , 'is reject' ) ; } );
promiseB
	.then( ( argRet ) => { console.log( argRet , 'is resolve' ) ; } )
	.catch ( ( argRet ) => { console.log( argRet , 'is reject' ) ; } );
promiseC
	.then( ( argRet ) => { console.log( argRet , 'is resolve' ) ; } )
	.catch ( ( argRet ) => { console.log( argRet , 'is reject' ) ; } );
/*
Start: A
Start: B
Start: C
x:B is reject
o:A is resolve
o:C is resolve
<allSettled finished normally>
	(3) [{…}, {…}, {…}]
		0: {status: 'fulfilled', value: 'o:A'}
		1: {status: 'rejected', reason: 'x:B'}
		2: {status: 'fulfilled', value: 'o:C'}
			length: 3
<allSettled finally>
*/

Promise.all() メソッドと Promise.allSettled() メソッドの違いは、失敗を検知してもそのまま待ち合わせを続けることです。
そして返される結果は、各プロミスの結果を含む配列が返されます。
配列は status プロパティと value または reason プロパティが含まれます。
status が完了:fulfilled では value プロパティが、失敗:rejected では reason プロパティが含まれます。

Promise.race()

Promise.race() は配列で指定されたプロミスのうち最初に解決(完了・失敗に関係なく)したプロミスを返します。そのプロミスが完了していれば .then() メソッドを実行し、失敗であれば .catch() メソッドを実行します。

let promiseA , promiseB , promiseC ;
promiseA = new Promisef( 'A' , true ) ;
promiseB = new Promisef( 'B' , false ) ;	// <--- 失敗になります。
promiseC = new Promisef( 'C' , true ) ;
//
Promise.race( [ promiseA , promiseB , promiseC ] )	// Promise.race()
	.then( ( argRet ) => { console.log( '<Race finished normally>' , argRet) ; } )
	.catch( ( argRet ) => { console.log( '<Race caught error>' , argRet) ; } )
	.finally( () => { console.log( '<Race finally>') ; } ) ;
//
promiseA
	.then( ( argRet ) => { console.log( argRet , 'is resolve' ) ; } )
	.catch ( ( argRet ) => { console.log( argRet , 'is reject' ) ; } );
promiseB
	.then( ( argRet ) => { console.log( argRet , 'is resolve' ) ; } )
	.catch ( ( argRet ) => { console.log( argRet , 'is reject' ) ; } );
promiseC
	.then( ( argRet ) => { console.log( argRet , 'is resolve' ) ; } )
	.catch ( ( argRet ) => { console.log( argRet , 'is reject' ) ; } );
/*
Start: A
Start: B
Start: C
o:C is resolve					<--- 最初に解決したプロミスは完了している
<Race finished normally> o:C	<--- then()メソッドを実行
<Race finally>					<--- 待ち合わせを終了
x:B is reject
o:A is resolve
*/

最初に解決したプロミスが失敗した場合は、.catch() メソッドを実行。

/*
Start: A
Start: B
Start: C
x:B is reject				<--- 最初に解決したプロミスは失敗している
<Race caught error> x:B		<--- catch()メソッドを実行
<Race finally>				<--- 待ち合わせを終了
o:C is resolve
o:A is resolve
*/

Promise.any()

Promise.any() は配列で指定されたプロミスのうち最初に完了したプロミスを返します。
.catch() メソッドは実行されません。

let promiseA , promiseB , promiseC ;
promiseA = new Promisef( 'A' , true ) ;
promiseB = new Promisef( 'B' , false ) ;	// <--- 失敗になります。
promiseC = new Promisef( 'C' , true ) ;
//
Promise.any( [ promiseA , promiseB , promiseC ] )	// Promise.any()
	.then( ( argRet ) => { console.log( '<Any finished normally>' , argRet) ; } )
	.catch( ( argRet ) => { console.log( '<Any caught error>' , argRet) ; } )
	.finally( () => { console.log( '<Any finally>') ; } ) ;
//
promiseA
	.then( ( argRet ) => { console.log( argRet , 'is resolve' ) ; } )
	.catch ( ( argRet ) => { console.log( argRet , 'is reject' ) ; } );
promiseB
	.then( ( argRet ) => { console.log( argRet , 'is resolve' ) ; } )
	.catch ( ( argRet ) => { console.log( argRet , 'is reject' ) ; } );
promiseC
	.then( ( argRet ) => { console.log( argRet , 'is resolve' ) ; } )
	.catch ( ( argRet ) => { console.log( argRet , 'is reject' ) ; } );
/*
Start: A
Start: B
Start: C
x:B is reject					<--- 最初に解決したプロミスは失敗
o:C is resolve					<--- 最初に完了したプロミス
<Any finished normally> o:C		<--- then()メソッドを実行
<Any finally>					<--- 待ち合わせの終了
o:A is resolve
*/

参考リンク

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