JavaScript Promise の基本的な使い方
JavaScript の Promise とは?
JavaScript の Promise は非同期処理の終了結果や戻り値を表すオブジェクトです。Promise を利用することで、非同期処理がとても簡単に実装できるようになります。
JavaScript Promise の状態
Promise の状態には次の状態があります。
- Pending - 終了待ち状態 (初期状態でもあります)
- Settled - 処理結果が出た状態
- Fulfilled - 正常終了状態
- Rejected - 失敗状態
非同期処理を行うときに、正常終了か失敗かの結果が出る前の状態が Pending です。 成功か失敗いずれかの結果が出た状態が Settled 状態です。
Settled の状態のうち、成功状態を Fulfilled、失敗状態を Rejected といいます。
Promise の基本的な使い方
Promise は非同期処理を簡単に扱うために使います。
非同期処理というのは、平たく言えば、時間のかかる処理を別途処理し、処理の終了時にその結果を受け取るタイプの処理です。
具体例としては、 fetch() を使ってネットワーク越しにデータを取得するような時間のかかる処理を行う場合に用いられます。
「fetch() の使い方」のコードを見ていただければわかるように、 処理の完了時には then() あるいは catch() という名前のメソッドが呼ばれています。 これらがまさに Promise のメソッドなのです。
fetch() を利用するだけなら、それが Promise であるということを意識する必要はないかもしれません。 しかし、 Promise は非同期処理を行うのに大変便利で、Promise によって複雑になりがちな非同期処理が簡潔に記述できるのでぜひ有効活用したいところです。
さて、Promise を利用する基本形を見てみましょう。
function myfunc(resolve, reject) {
// 時間のかかる処理
// 正常終了なら resolve を呼ぶ
resolve(123)
// エラーの場合
// reject('Error!')
}
function onFulfilled(value) {
console.log('fulfilled')
console.log(value)
}
function onRejected(reason) {
console.log('rejected')
console.log(reason)
}
const promise = new Promise(myfunc)
promise.then(onFulfilled).catch(onRejected)
// fulfilled
// 123
この例では myfunc() という関数で時間のかかる処理を行うことを想定しています。 引数に二つの関数オブジェクトを受け取っており、処理の成功時には一つ目の引数で渡された resolve を、 失敗時には二つ目の引数で渡された reject を呼ぶことにします。
19行目では、この myfunc() 関数を用いて Promise を作成しています。
20行目のように記述することで、正常終了の場合に then() に渡した関数が呼ばれ、 エラー時に catch() に渡した関数が呼ばれます。
この場合では処理の成功時、すなわち myfunc() で resolve() を呼び出した時に onFulfilled() 関数が呼ばれます。また処理の失敗時、すなわち myfunc() 関数で reject() を呼び出した時に onRejected() が呼び出されます。
resolve() に渡したデータが onFulfilled() 関数に渡され、 reject() に渡したデータが onRejected() に渡されます。
さて、上記の内容は通常は、アロー関数 () => などを利用して、もっと簡潔に記述されるのが普通です。
上記の内容と同様の内容は、次のようにかけます。
function myfunc() {
return new Promise((resolve, reject) => {
// 時間のかかる処理
// 正常終了なら resolve を呼ぶ
resolve(123)
// エラーの場合
// reject('Error!')
})
}
myfunc()
.then((value) => {
console.log('fulfilled')
console.log(value)
})
.catch((reason) => {
console.log('rejected')
console.log(reason)
})
Promise のチェインニング
Promise が fulfill した時に、その Promise オブジェクトの then() メソッドが呼び出されますが、この then() メソッドはさらに新しい Promise を返します。
then() メソッドが返す Promise オブジェクトは、 then() メソッドに渡された関数の戻り値を使って fulfill されます。そして、その値は then() メソッドに渡す関数に渡されます。
このように、 then() メソッドが新しく Promise オブジェクトを返すので、 Promise は foo().then().then() のように数珠繋ぎに非同期処理を行うことができます。これを Promise のチェインニング (chaining) といいます。
次のコード例では then() メソッドに渡した関数の戻り値が、次の then() メソッドに渡す関数への引数として次々渡されていることが確認できます。
function foo() {
return new Promise((resolve, reject) => {
resolve('A')
})
}
foo()
.then((value) => {
console.log(`1st: ${value}`)
return 'B'
})
.then((value) => {
console.log(`2nd: ${value}`)
return 'C'
})
.then((value) => {
console.log(`3rd: ${value}`)
})
.catch((reason) => {
console.log('catch!')
console.log(reason)
})
// 1st: A
// 2nd: B
// 3rd: C
Promise でのエラー処理
Promise でのエラー状態は reject() を呼ぶことでセットされます。
それにより、Promise の catch() に渡した関数が呼び出されます。このときに reject() に渡したオブジェクトが渡されます。
このときに、任意のオブジェクトを渡すことができます。
良く行われる方法としては、次のように Error オブジェクトを作成し、 catch() 側でオブジェクトの型を判定する方法です。
function myfunc() {
return new Promise((resolve, reject) => {
//resolve(123)
// エラーの場合
reject(new Error('Error!'))
})
}
myfunc()
.then((value) => {
console.log('fulfilled')
console.log(value)
})
.catch((reason) => {
console.log('rejected')
if (reason instanceof Error) {
console.log(reason.message)
} else if (reason instanceof SyntaxError) {
// ...
} else {
console.log(reason)
}
})
Promise チェインニングのエラー処理
Promise のチェインニングのときは、エラーを throw することによって Promise をエラー状態にできます。 そして、 throw した値は、続く catch() メソッドに渡す関数に渡されます。
function foo() {
return new Promise((resolve, reject) => {
resolve('A')
})
}
foo()
.then((value) => {
console.log(`1st: ${value}`)
return 'B'
})
.then((value) => {
console.log(`2nd: ${value}`)
throw new Error('An error occurred!') // エラー発生
return 'C' // 呼ばれない
})
.then((value) => {
console.log(`3rd: ${value}`)
})
.catch((reason) => {
console.log('catch!')
console.log(reason)
})
// 1st: A
// 2nd: B
// catch!
// Error: An error occurred!
// at ...