【JSリファレンス】Promise.prototype.then()
更新日:2024/06/14
Promise.prototype.then()は、Promiseオブジェクト(Promiseコンストラクターのインスタンス)のプロトタイプチェーンに組み込まれるメソッドです。
onFulfilledおよびonRejectedハンドラーを登録します。
■構文
Promise.prototype.then ( onFulfilled, onRejected )
- onFulfilled: Promiseが解決した時呼び出される関数
- onRejected: Promiseが失敗(拒否)した時、およびエラースロー時に呼び出される関数
onFulfilledまたはonRejectedに関数以外の値を指定した場合、直前の状態と値で解決または拒否されます。
■onFulfilledおよびonRejected関数の構文
onFulfilled( value ){ } onRejected( value ){ }
- value: 直前の解決または拒否時の値
※Promiseオブジェクトの場合は、そのPromiseオブジェクトが fulfilled(履行)、rejected(拒否)に移行するまで待ちます。
■then()について
ここではthen()について記述していますが、catch()およびfinally() についても、同じことが適用されます。
▶then()は新しいオブジェクトを返す
then()はハンドラーを登録した後、新しいオブジェクトを生成して返します。
Promiseは、次のように then()、catch()、finally()をメソッドチェーンで任意の回数呼び出すことができます。
// メソッドチェーンでの呼び出し
const p = new Promise( ()=>{})
.then( ()=>{})
.catch( ()=>{})
.finally( ()=>{} )
.then( ()=>{})
.catch( ()=>{})
.finally( ()=>{} );
このコードを見ると、new Promise()で生成したオブジェクトのメソッドを何度も呼び出しているように見えます。
実際には各メソッドは新しいオブジェクトを生成して返します。
そのため、メソッドは生成されたオブジェクトが1回だけ呼び出しています。
また変数p の値は最後の finally() が生成したpromiseオブジェクトです。
ハンドラーはメソッドを呼び出したオブジェクトに登録されます。
上記コードを実行すると、次のようなイメージになります。
p1 = new Promise( ()=>{}) // onFulfilled:F1 onRejected:F2
p2 = p1.then( F1 , F2 ) // onFulfilled:undefined onRejected:F3
p3 = p2.catch( F3 ) // onFulfilled:F4 onRejected:F4
p4 = p3.finally( F4 ) // onFulfilled:F5 onRejected:F6
p5 = p4.then( F5 , F6 ) // onFulfilled:undefined onRejected:F7
p6 = p5.catch( F7 ) // onFulfilled:F8 onRejected:F8
p7 = p6.finally( F8 );
finally()はonFinallyハンドラーですが、内部ではonFulfilledとonRejectedとして登録されます。
▶then()が生成したpromiseオブジェクトの状態遷移
new Promise()で生成されたPromiseオブジェクトはコールバック関数で resolve() または reject()が呼び出されると状態が遷移します。
その際オブジェクトにハンドラーが登録されていたら、ハンドラーを呼び出すPromiseジョブがタスク登録されます。
タスクは現在のプログラムコードが終了して、イベントループに戻った後で実行されます。
そのため、new Promise()で生成したオブジェクトが即座に解決されても、then()が生成したオブジェクトの状態は pending のままです。
const p1 = new Promise( (resolve,reject)=>{
resolve(100);
});
const p2 = p1.then((v)=>{});
console.log( "p1" , p1 );
console.log( "p2" , p2 );
// 結果:
// p1 Promise { <state>: "fulfilled", <value>: 100 }
// p2 Promise { <state>: "pending" }
then()が生成したオブジェクトの状態は、親に登録されたハンドラーが値を返した時点で遷移します。
次のコードは、then()で3000ミリ秒でfulfilledに遷移するPromiseオブジェクトを返すハンドラーを登録しています。
ハンドラー内では遷移前後のPromiseオブジェクトの状態を、setTimeout()を使用して確認しています。
// 指定時間待機後 p2 を出力する関数
const s = t =>setTimeout( ()=>{
console.log( `${t}ミリ秒経過` , p2);
},t );
const p1 = new Promise( (resolve,reject)=>{
resolve(100);
});
const p2 = p1.then((v)=>{
// このハンドラーは p1 に登録されている
// ハンドラー開始直後の p2の状態
console.log( "then():" , p2 );
s( 2000 ); // 2000ミリ秒でタイマーセット
s( 4000 ); // 4000ミリ秒でタイマーセット
// 3000ミリ秒でfulfilledに遷移するPromiseオブジェクトを返す
return new Promise(
resolve=>setTimeout(
()=>{
console.log( "3000ミリ秒経過 resolve()" );
resolve(200)
},3000)
);
} );
// 結果
// then(): Promise { <state>: "pending" }
// 2000ミリ秒経過 Promise { <state>: "pending" }
// 3000ミリ秒経過 resolve()
// 4000ミリ秒経過 Promise { <state>: "fulfilled", <value>: 200 }
上記コードの p2 にthen()等でハンドラーが登録されている場合、状態遷移後にハンドラーを実行するジョブがタスク登録され、ハンドラーの結果で次のPromiseオブジェクトの状態が遷移します。
その結果ハンドラーが呼び出され、と処理が連鎖します。
▶then()の複数回呼び出し
メソッドチェーンを使用すると1回のみですが、Promiseオブジェクトは3つのメソッドを複数回呼び出すことができます。
const p1 = new Promise( (r1,r2)=>r2(100));
const cl = console.log;
p1.then( undefined, v=>cl( `1:${v}` ) );
p1.then( undefined,v=>cl( `2:${v}` ) );
p1.then( undefined, v=>cl( `3:${v}` ) );
p1.catch( v=>cl( `4:${v}` ) );
p1.catch( v=>cl( `5:${v}` ) );
p1.catch( v=>cl( `6:${v}` ) );
p1.finally( ()=>cl( `7:` ) ).catch( v=>cl( `10:${v}` ) );
p1.finally( ()=>cl( `8:` ) ).catch( v=>cl( `11:${v}` ) );
p1.finally( ()=>cl( `9:` ) ).catch( v=>cl( `12:${v}` ) );
// 結果:
// 1:100
// 2:100
// 3:100
// 4:100
// 5:100
// 6:100
// 7:
// 8:
// 9:
// 10:100
// 11:100
// 12:100
■使用例
▶then() で拒否する
new Promise(
(r)=>{
r( 100 );
})
.then(
v =>{
console.log( v );
return Promise.reject( v + 100 ); // 拒否
}
)
.then(
()=>{},
v => {
console.log( v );
}
);
// 結果
// 100
// 200
▶then() でPromiseを返す
new Promise(
(r)=>{
const watiTime = 1000;
console.log( `${watiTime}ミリ秒停止します` );
setTimeout( ()=>r(watiTime) , watiTime);
})
.then(
v =>{
console.log( `${v}ミリ秒再停止します` );
return new Promise(
r=>{
setTimeout( ()=>r(v*2) , v);
});
}
)
.then(
v=>{
console.log( `合計${v}ミリ秒停止から復帰しました` );
},
);
// 結果
// 1000ミリ秒停止します
// 1000ミリ秒再停止します
// 合計2000ミリ秒停止から復帰しました
▶then() でthen()を複数回呼び出したPromiseを返す
const sleep = t => new Promise( r=>{
let {sumTime,waitTime} = typeof t === "object"
? t : { sumTime:0 , waitTime:t };
console.log( `${waitTime}ミリ秒停止します` );
setTimeout(()=>{
sumTime += waitTime; waitTime *= 2;
r({sumTime,waitTime});
},waitTime);
});
new Promise(
(r)=>{
r(sleep(100));
})
.then(
v =>{ // 【1】
return sleep(v) // 【2】
.then( sleep ) // 【3】
.then( sleep ) // 【4】
.then( sleep ); // 【5】
}
)
.then(
v=>{ // 【6】
console.log( `合計${v.sumTime}ミリ秒停止しました` );
},
);
// 結果
// 100ミリ秒停止します
// 200ミリ秒停止します
// 400ミリ秒停止します
// 800ミリ秒停止します
// 1600ミリ秒停止します
// 合計3100ミリ秒停止しました
【1】の関数は、最後のthen() (【5】) で生成された Promiseオブジェクトを返します。
そのため、【2】から順番に解決していき【5】が解決された時点で、【6】の関数が呼び出されます。
▶エラー発生時の動作
エラー発生時は、onRejectedハンドラーが呼び出されます。
new Promise(
(r)=>{
throw new Error("エラー1");
})
.then(
() =>{},
v =>{ // onRejectedハンドラー
console.log( v.message );
return "No Error";
}
)
.then(
v=>{ // onFulfilledハンドラー
console.log( v );
throw new Error("エラー2");
},
)
.then(
() =>{},
v =>{ // onRejectedハンドラー
console.log( v.message );
}
);
// 結果
// エラー1
// No Error
// エラー2
▶非同期処理内エラー発生時の動作
非同期処理内で発生したエラーは、ハンドラーで補足できません。
new Promise(
(r)=>{
setTimeout(()=>{
throw new Error("エラー1");
},1000);
})
.then(
() =>{},
v =>{ // 呼び出されない
console.log( v.message );
}
);
▶引数に関数以外を指定した場合の動作
then()の引数に関数以外を指定した場合、次のPromiseオブジェクトのハンドラーが呼び出されます。
new Promise(
(r)=>{
r(100);
})
.then(
undefined // 関数ではない
// 次の onFulfilled ハンドラを呼び出す
).then(
v =>{
console.log(`受け取った値:${v}`);
}
);
// 結果
// 受け取った値:100
更新日:2024/06/14
スポンサーリンク
記事の内容について

こんにちはけーちゃんです。
説明するのって難しいですね。
「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。
裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。
掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。
ご意見、ご指摘はこちら。
https://jsref.affi-sapo-sv.com/info.php
このサイトは、リンクフリーです。大歓迎です。