前回までのあらすじ
非同期処理を平行に実行する場合はPromise.all
やPromise.race
を使えばよいのですが、非同期処理で受け取った値を使って次の処理を行いたいなど順番(同期的)に実行したい場合のメモ
Promise~thenでのメソッドチェーン
たぶん一番正当なやり方。
Promiseオブジェクトを返す関数を実行してresolve
された時に実行されるthen
メソッド内で、Promiseオブジェクトを返す関数をreturnすることで、Promise.prototype.then
のメソッドチェーンができる。
エラーになるとcatch
に処理が流れる。
実行される関数
function myFunc(a, b = 0) { return new Promise((resolve, reject) => { setTimeout(() => { if(a) { let res = (a + b) * 2 console.log(`(${a} + ${b}) * 2 = ${res}`) resolve( res ) } else { reject(`Error a is ${a}`) } }, 10) }) } function onReject(error) { console.log('>> onReject', error) }
Promise.prototype.thenのメソッドチェーン
myFunc(1).then((res1) => { // res1 => 2 return myFunc(2, res1) }) .then((res2) => { // res2 => 8 return myFunc(3, res2) }) .then((res3) => { // res3 => 22 console.log('> Promaise Complete', res3) }) .catch(onReject) // => (1 + 0) * 2 = 2 // => (2 + 2) * 2 = 8 // => (3 + 8) * 2 = 22 // => > Promaise Complete 22
エラーになる場合
myFunc(1).then((res1) => { // res1 => 2 return myFunc(0, res1) // => reject になる }) .then((res2) => { return myFunc(3, res2) }) .then((res3) => { console.log('> Promaise Complete', res3) }) .catch(onReject) // => (1 + 0) * 2 = 2 // => >> onReject error a is 0
エラーが発生するとそれ以降のthen
はスキップされる。
わかりやすいですね!
promise.then(successCallback, failureCallback) の場合
Promiseを使う - JavaScript | MDNに書かれているpromise.then(successCallback, failureCallback)
で同期的に実行させようとする場合。
myFunc(1).then((res1) => { myFunc(2, res1).then((res2) => { myFunc(3, res2).then((res3) => { console.log('> Promaise Complete', res3) }, (error) => console.log('Error 3', error)) }, (error) => console.log('Error 2', error)) }, (error) => console.log('Error 1', error)) // => (1 + 0) * 2 = 2 // => (2 + 2) * 2 = 8 // => (3 + 8) * 2 = 22 // => > Promaise Complete 22 // Errorになる場合 myFunc(1).then((res1) => { myFunc(0, res1).then((res2) => { myFunc(3, res2).then((res3) => { console.log('> Promaise Complete', res3) }, (error) => console.log('Error 3', error)) }, (error) => console.log('Error 2', error)) }, (error) => console.log('Error 1', error)) // => (1 + 0) * 2 = 2 // => Error 2 Error a is 0
エラーの場合は先のthen
をメソッドチェーンした場合と同じで、エラーになるとそれ以降の処理には流れず、エラーの時点で処理は完了になります。
then
の第一引数内で次のPromiseオブジェクトを返す関数を実行する必要があるので、同期処理が多くなるとコードのようにネストして見通しがあまり良くない感じです。ただ、エラーの処理をそれぞれ別に設定できるのはメリットかも知れません。
promise.then(successCallback, failureCallback) でthenのメソッドチェーンにした場合
ここからチョットマニアックかも。
myFunc(1) .then( (res1) => myFunc(0, res1), (error) => console.log('Error 1', error) ) .then( (res2) => myFunc(3, res2), (error) => console.log('Error 2', error) ) .then( (res3) => console.log('> Promaise Complete', res3), (error) => console.log('Error 3', error) ) .catch(onReject) // => (1 + 0) * 2 = 2 // => Error 2 Error a is 0 // => > Promaise Complete undefined
上の例では、
- 1つめの
then
: successCallback =>muFunc(0, res1)
(res1は2) - 2つめの
then
: failureCallback =>console.log('Error 2', error)
- 3つめの
then
: successCallback =>console.log('> Promaise Complete', res3)
(res3はundefined
)
then
の第二引数にfailureCallback
が渡されていると、エラーの時にcatch
に流れるのではなく、failureCallback
が呼ばれ、メソッドチェーンしてる次のthen
に処理が流れるようです。
次のthenに流れた時に渡される引数について
先の例ではエラーになった時のコールバックではreturn console.log('Error 2')
が実行されundefined
が返されているので、エラーで次のthen
に渡される引数が「常にundefined
」なのか「コールバック関数で返された値」がなのかが判断しきれません。
そこで、明示的に値を返して試してみます。
myFunc(0) .then( (res1) => myFunc(2, res1), (error) => { console.log('Error 1') return 'Foo' }) .then( (res2) => console.log('> Promise Complete:', res2), (error) => console.log('Error2', error) ) // => Error 1 // => > Promise Complete: Foo
どうやら、エラーコールバックで返される(return
される)値が次のthen
内のsuccessCallback
に渡され実行されるようです。
Promise.reject()
を返せば次のthen
はエラーコールバックの方が呼ばれる
明示的にreturn Promise.reject()
すると、次のthen
の処理はエラーコールバックfailureCallback
の方に処理が流れました。
myFunc(0).then( (res1) => myFunc(2, res1), (error) => { console.log('Error 1') // Promise.reject を返す return Promise.reject('Reject 1') }) .then( (res2) => console.log('> Promise Complete:', res2), (error) => console.log('Error2', error) ) // => Error 1 // => Error2 Reject 1
promise.then(successCallback, failureCallback).then(...)
の形式でメソッドチェーンをした場合は、
- エラーの場合は
catch
ではなく、第二引数のfailureCallback
が実行される - 先の処理の成功失敗に関わらず、次の
then
が呼ばれる - 次の
then
のコールバックに渡される値は、先のコールバック内でreturn
された値 - 次の
then
でエラーに処理を流したい場合は、return Promise.reject("エラーコールバックに渡す引数")
とする
という感じっぽいです。
先の非同期処理が成功しようが失敗しようが処理を待って次の処理を実行することができるので、値があれば値を使って、無ければ無いなりで必須な共通処理とか使い方はあるかもしれません。
myFunc(0) .then( (res1) => { console.log('Success 1: res1 is ', res1) return myFunc(res1) }, (error1) => { console.log('Error 1: default val 1') return 1 } ) .then( (res2) => { // 前のthenでPromise.rejectされないので、必ずココが実行される console.log('Common callback: res2 is', res2) return myFunc(res2) }, (error2) => { console.log('Error 2:', error2) return Promise.reject('ERROR 2') } ) .then( (res3) => { console.log('Promise complete:', res3) }, (error) => { console.log('Error3', error) } ) // => Error 1: default val 1 // => Common callback: res2 is 1 // => (1 + 0) * 2 = 2 // => Promise complete: 2
途中からthen~catch
のメソッドチェーンにした場合
TASK
function myFunc2(a, b = 0) { return new Promise((resolve, reject) => { setTimeout(() => { if(!a) { reject(`Error a is ${a}`) } let res = (a + b) * 2 console.log(`(${a} + ${b}) * 2 = ${res}`) resolve( res ) }, 10) }) }
1. 成功した場合は、そのまま次のthen
が実行される
myFunc2(1) .then( (res1) => { console.log('Success 1: res1 is ', res1) return myFunc(2, res1) }, (error1) => { console.log('Error 1') return 2 } ) .then( (res2) => { console.log('Success 2: res2 is ', res2) } ) .catch( (error) => { console.log("Error catch", error) } ) // => (1 + 0) * 2 = 2 // => Success 1: res1 is 2 // => (2 + 2) * 2 = 8 // => Success 2: res1 is 8
2. 失敗し、コールバック関数内で単に値をreturn
した場合も、そのまま次のthen
が実行される
myFunc2(0) .then( (res1) => { console.log('Success 1: res1 is ', res1) return myFunc(2, res1) }, (error1) => { console.log('Error 1') return 2 } ) .then( (res2) => { console.log('Success 2: res2 is ', res2) } ) .catch( (error) => { console.log("Error catch", error) } ) // => (0 + 0) * 2 = 0 // => Error 1 // => Success 2: res2 is 1 ... 先のエラー関数でreturnされた値が渡される
3. 失敗し、コールバック関数内でPromise.reject()
を返した場合、catch
に処理が流れる
myFunc2(0) .then( (res1) => { console.log('Success 1: res1 is ', res1) return myFunc(2, res1) }, (error1) => { console.log('Error 1') return Promise.reject("Reject!") } ) .then( (res2) => { console.log('Success 2: res2 is ', res2) } ) .catch( (error) => { console.log("Error catch", error) } ) // => (0 + 0) * 2 = 0 // => Error 1 // => Error catch Reject! ... 先のコールバック関数で呼ばれたPromise.rejectに渡された値が引数で渡される
4. 成功したコールバック関数内でPromise.reject
が返された場合もcatch
に処理が流れる
myFunc2(1) .then( (res1) => { console.log('Success 1: res1 is ', res1) // 成功したけど、reject return Promise.reject(`res 1 is ${res1}`) }, (error1) => { console.log('Error 1') return Promise.reject("Reject!") } ) .then( (res2) => { console.log('Success 2: res2 is ', res2) } ) .catch( (error) => { console.log("Error catch", error) } ) // => (1 + 0) * 2 = 2 // => Success 1: res1 is 2 // => Error catch res 1 is 2
途中からthen~catch
のメソッドチェーンを繋ぐことは問題ないようなので、successCallback
, failureCallback
を使った場合の特性を知っていれば、特定のエラー処理だけ分けたいとかって場合などに使えるかもです。(あまりお行儀はよくなさそうだけど...)
さっさとawait /async
に行けばいいのに色々と気になって試してしまいました。(学習が遅い言い訳
- 作者:WINGSプロジェクト 齋藤 新三
- 発売日: 2018/07/20
- メディア: 単行本(ソフトカバー)