Rso's Jotter

日々の開発の知見のメモやその他雑記

JavascriptのPromiseコード間違い探し

Javascriptのコードをレビューしていたら以下のような少々ツッコミどころのあるコードを見つけましたのでメモしておきます。 内容は一部サマっていますが、以下のようなコードです。

function asyncFunction() {
  return new Promise((resolve, reject) => {

    //本当は別の非同期処理
    setTimeout(function () {
      resolve('非同期処理完了');
    }, 3000);

  }).then( (result) => {
    // 成功した処理
    console.log(result)
    return true;
  }).catch( (_err) => {
    //失敗した処理
    console.error(_err)
    return false
  });
}

//メイン処理
if(asyncFunction()){
   console.log("正常終了処理")
}else{
   console.log("異常終了処理")
}

やりたい処理の意図は以下です。

  • メイン処理でPromise を使った非同期処理を行う関数asyncFunctionを呼び出している。
  • その呼び出し結果に応じて、呼び出し元がその関数呼び出しが正常か異常か判断しようとしている。

ということがやりたかったのですが、さてこのコードはどこがおかしいでしょうか。

一見すると(?)普通に動くように見えるかもしれませんが、同期処理と非同期処理の理解が混在していることが伺えます。 おかしなところを一言で言うと、以下の部分でasyncFunctionはtrue/falseを返さない。と言うことになります。

  if(asyncFunction()){

Promiseのthenのなかで return true とかかくとそのまま trueが帰ってくるように錯覚したと思うのですが、 Promise.then() 内で return したら Promiseオブジェクトが返されます . なので たとえ上記のasyncFunctionがrejectされたとしても、 ifの判定はelseの方に行きません。

MDN のサイトにもPromise.thenの戻り値について以下のように記載されています。

then メソッドは Promise を返すので、メソッドチェーンができます。 関数が then にハンドラーとして渡されると Promise を返します。同じ Promise がメソッドチェーンの次の then に現れます。

上記はasyncFunction の結果を受けてから 呼び出し元で処理を実行したい意図があったかもしれませんが、非同期処理となるために想定された順序で実行されません。

ですのでありきたりな解決策ですが、正しくは呼び出し元でも thenを使いましょう、と言うことになります。

function asyncFunction() {
  return new Promise((resolve, reject) => {
    console.log("promise start")

    //本当は別の非同期処理
    setTimeout(function () {
      resolve('非同期処理完了');
    }, 3000);

  }).then( (result) => {
    // 成功した処理
    console.log(result)
    return true;
  }).catch( (_err) => {
    //失敗した処理
    console.error(_err)
    return false
  });
}

//thenとcatchに変更
asyncFunction().then( () => {
   console.log("正常終了処理")
}).catch( () => {
   console.log("異常終了処理")
})

これで正しくasyncFunctionの結果を待ってから後続処理が実行されました。これでおっけーでしょうか。 実はまだもう一つ問題があります。 asyncFunction内でcatchしてそのままreturnしているものをもう一度catchしようとしています。 catchメソッド内で単純にreturnした場合、 それは resolve扱いになるので、 asyncFunction呼び出し後の "異常終了処理"は実行されません。

なので対策としては、 2回もcatchを入れるのをやめるか、どうしても2段にしたいのであれば、 1つ目のcatchのなかで rejectを呼んでやる必要があります。

function asyncFunction() {
  return new Promise((resolve, reject) => {
    console.log("promise start")

    //本当は別の非同期処理
    setTimeout(function () {
      resolve('非同期処理完了');
    }, 3000);

  }).then( (result) => {
    // 成功した処理
    console.log(result)
    return true;
  })
  .catch( (_err) => {
    //失敗した処理
    console.error(_err )
    // rejectに変更
    reject(false) //どうしても外側でもcatch処理行いたい
  });
}

asyncFunction().then( () => {
   console.log("正常終了処理")
}).catch( () => {
   console.log("異常終了処理")
})

これでエラー時は2つ目もcatch処理を実行させることができました。

ちょっと例のコードが実施の処理を省略しているので分かりづらかったですが、 非同期の処理とPromiseの仕様を理解する上で良いミス(題材)だと思ったので、記載しました。