はじめに
Promiseが登場する以前はコーバック地獄や非同期処理は実行順序を制御できない問題がありました。
Promiseはこれらの問題を解決するために考案されました。
Promiseとは
Promiseは非同期処理の状態(完了もしくは失敗の結果)およびその結果の値を表します。
Promiseの状態は待機(pending
)、完了 (fulfilled
)、失敗 (rejected
)の3種類の状態があります。
処理が完了したらfulfilled
、失敗したらrejected
、完了も失敗もしていない時はpending
状態です。
Promiseの使い方
まず下記のコードを見てください。Promiseオブジェクトを作成しています。
let promise = new Promise((resolve, reject)=> { // ここに処理を書く });
Promise
の引数には関数(コールバック関数)を渡し、第一引数にresolve
、第二引数にreject
(任意)を設定します。
resolve
関数が呼ばれたら、処理が正常に完了したことを示し、reject
関数が呼ばれたらエラーが発生(失敗)したことを示します。
いまいちわからないと思うので、次を見てください。
Promiseの状態について
待機( pending )
まず、処理を記載してないpromise
(初期状態)は以下の値となります。
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "pending"
[[PromiseResult]]: undefined
PromiseState : "pending"
は待機、つまり完了も失敗もしていない初期状態です。
PromiseResult : undefined
は処理の計算結果です。初期値は undefined
です。
完了( fulfilled )
次に、PromiseState
をfulfilled
にしてみましょう。
let promise = new Promise((resolve, reject)=> {
resolve('成功したよ');
})
処理にresolve(value)
を呼ぶことで、状態をfulfilled
に、PromiseResult
の値はvalue
で設定できます。
Promise {<fulfilled>: '完了したよ'}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: "成功したよ"
PromiseState
が fulfilled
に、PromiseResult
が成功したよ
に変わっているのがわかります。
失敗( rejected )
同じ要領で、PromiseState
をrejected
にしてみましょう。
let promise = new Promise((resolve, reject)=> {
reject(new Error('失敗しちゃった'));
})
処理にreject(error)
を呼ぶことで、状態をrejected
に、PromiseResult
の値はerror
で設定できます。
※reject
のerror
にはError オブジェクトを利用するのが一般的です。
Promise {<rejected>: Error: 失敗しちゃった
[[Prototype]]: Promise
[[PromiseState]]: "rejected"
[[PromiseResult]]: Error: 失敗しちゃった
ちゃんとPromiseState
は
に、rejected
PromiseResult
はError:"失敗しちゃった"
になっていますね。
Promise
の状態がpending
からfulfilled
あるいはになった後、その状態を変更することはできません。 この後説明する、
rejected
then()
やcatch()
メソッドは呼び出すたびに新しいPromiseオブジェクトを返しています。
PromiseState
とは内部プロパティです。 内部プロパティは直接アクセスすることができないので、
PromiseResult
then()
やcatch()
メソッドを用いてアクセスします。
これらの3種類の状態とPromiseチェーンを使用して、一連の非同期処理をスマートに表現していきます。
Promiseチェーン
Promiseの最も重要な利点の一つが、一連の非同期処理を、連続するthen()
、catch()
、finally()
メソッドの呼び出しとして表現できることです。
これらのメソッドは全てPromiseオブジェクトを返します。なので、処理を入れ子にする必要がなく、チェーン(連鎖)させることができます。
fetch() // fetchメソッドはPromiseオブジェクトを返します
.then()
.then()
.then()
.catch()
.finally()
メソッドチェインと同じ原理なので、解説はこちらを参考にしてください。
then()
then()
は最も基本的で重要なメソッドです。
下記のコードを見てください。
let promise = new Promise((resolve, reject)=> {
resolve('完了したよ');
}).then(
result => console.log(result), // promiseの状態がfulfilledの場合、実行
error => console.log(error) // promiseの状態がrejectedの場合、実行
)
Promise
オブジェクトにthen( function(result), function(error) )
を繋げて、新たな処理を実行しています。
then()
はPromiseの状態がfulfilled
かrejected
になるまで、実行されません。
then()
1. 第一引数の関数は、Promise が完了(fulfilled
)されたときに実行され、その結果を受け取ります。
2. 第二引数の関数は、promise が失敗(rejected
)されたときに実行され、エラーを受け取ります。(完了の場合だけを扱いたい場合、第一引数だけ指定すれば良い)
第一引数の関数が呼び出されたとき、Promiseが満たされたと言い、
第二引数の関数が呼び出されたとき、Promiseが失敗したと言います。
またコールバック関数の引数(result
やerror
)には、Promise
オブジェクトのPromiseResult
の値を受け取ることができます。(今回は'完了したよ'
という文字列)
上記のコードではPromiseの状態がfulfilled
なので、result => console.log(result)
が実行され、コンソールに完了したよ
と表示されます。

Promiseオブジェクトが失敗(rejected
)の場合は第二引数の関数が実行されます。

Promiseが完了か失敗の状態によって、実行する処理を制御できるのがthen()
メソッドです。
catch()
catch()
はthen()
のエラーにだけ特化したようなメソッドです。
then()
は第一引数にnull
を設定でき、エラーの場合のみ実行させたい場合、then(null, function(error))
のような書き方ができます。
then(null, function(error))
とcatch(function(error))
は同じ動きをします。
let promise = new Promise((resolve, reject)=> {
reject(new Error('失敗しちゃった'));
}).catch(
error => console.log(error) // 前のpromiseの状態がrejectの場合、実行
)
// 実行後、コンソールにerrorが表示される
Error: 失敗しちゃった
at <anonymous>:2:10
at new Promise (<anonymous>)
at <anonymous>:1:15
catch()
はthen(null, function(error))
を簡略化したものです。一般的にcatch()
を使います。
finally
例外処理で try {...} catch {...}
に finally
節があるように、Promise にも finally()
があります。
finnally()
はPromiseが完了、失敗どちらの状態であっても、後始末(開いたファイルを閉じる、ネットワークの接続を閉じるなど)をする必要がある際に最適なメソッドです。
let promise = new Promise((resolve, reject)=> {
reject(new Error('失敗しちゃった'));
}).catch(
error => console.log(error) // 前のpromiseの状態がrejectの場合、実行
).finally(()=> {
console.log('ファイナリーー') // promiseの状態がfulfilledかrejectのどちらの場合でも、実行
})

finally()
の特徴として、
1. 引数は渡されません。なので、完了(fulfilled
)か失敗(rejected)の判断ができません。
2. finally()
の結果はthen()
やcatch()
に引数として渡すことができます。
一連の流れをまとめたものです。

then()
やcatch()
は新しいPromiseオブジェクトを返します。then()
やcatch()
にコールバック関数を引数として渡した時にthen()
は、新しいPromiseを返します。
このPromiseはまだpending
状態です。
後で、非同期にthen()
のコールバック関数がなんらかの処理を行い、返値を返します。
Promiseはその返値によって解決されます。(resolve
)
返値が値もしくは何も返さなかった場合は、Promiseはすぐに満たされます。
これの繰り返しです。
ここで、then()
の完了時と失敗時の処理後のPromiseの値を見てみましょう。
let promise = new Promise((resolve, reject)=> {
resolve('完了したよ');
}).then(
result => console.log(result), // 前のpromiseの状態がfulfilledの場合、実行
)
let promise = new Promise((resolve, reject)=> {
reject(new Error('失敗しちゃった'));
}).then(null,
error => console.log(error) // 前のpromiseの状態がrejectの場合、実行
)
// then(result => console.log(result))実行後のPromise
Promise {<fulfilled>: undefined}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: undefined
// then(error => console.log(error))実行後のPromise
Promise {<fulfilled>: undefined}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: undefined
どちらのPromiseも同じ状態(fulfilled
)になっています。
なぜ?と思った人はthen()
の認識が違っています。
then()
メソッドは単にコールバック関数を登録するメソッドと思ってください。addEventListener
と同じようにイベントが発生したら、関数を実行するようなものです。
then()
の場合はPromiseの状態によって、渡されたコールバック関数を実行するだけです。
Promiseがrejected
の状態になるのは処理中にエラーや例外が発生して、拒否された場合です。
逆に、then()
のコールバック関数の処理が無事終了し、返り値が通常の値、もしくは何も返さない時は、fulfilled
の状態になり、返り値がPromiseResult
にセットされます。(何も返さない時はundefined
)
上記の例はただコンソールにerror
を表示して、返り値に何も返していないので、State:fulfilled
、Result:undefined
です。

async関数/await式
非同期的コードは、通常の同期的コードと違って、値を返したり、例外をスロー(throw文)したりできません。
満たされたPromise値は、同期的コードの戻り値のようなものです。
しかし、場合によってはコードが冗長になってしまいます。
async関数/await式を使用すれば、Promiseをより簡潔に書くことができます。
await式
await
はPromiseを受け取り、戻り値やスロー(throw文)された例外に変換します。
また、Promiseが完了するまで、何も実行しません。
let promise = new Promise((resolve, reject)=> {
resolve('成功したよ');
})
// PromiseをPromiseResultの値に変換している
await promise
/=> '成功したよ'
let promise = new Promise((resolve, reject)=> {
reject(new Error('失敗しちゃった'));
})
// PromiseをPromiseResultの値に変換している(Uncaught errorが起きるが、、)
await promise
/=> Error: 失敗しちゃった
await
を使うコードはそのコード自身も非同期になります。
またawait
を使えるのは、async
を使って宣言された関数の中だけです。
上記のコードは開発者コンソールでは使用できますが、通常はasyncを宣言しないとawaitは使用できません。
async関数
先ほどのコードをasync
を使用して書き直したコードです。
async function getPromise() {
let promise = new Promise((resolve, reject)=> {
resolve('成功したよ');
})
// PromiseをPromiseResultの値に変換している
await promise
/=> '成功したよ'
}
関数にasyncを宣言するとその関数の戻り値はPromiseになります。
通常の関数でawait
を使うことはできません 非async
関数でawait
を使おうとした場合、構文エラーになります。
await
はトップレベルのコードでは動作しません。 このような書き方ではエラーが起きます。let response = await fetch(URL);
async
関数でラップしてやりましょう。
まとめ
JavaScriptの非同期通信を理解する上ではとても重要な要素なので、しっかり理解しておいたほうが良いです。
MDN Web Docsではちゃんとした例で説明しているので、わかりやすいです。
参考にしてください。

またこの記事もわかりやすいです。

コメント