ES6(Promise)

はじめに

非同期APIの一つであるPromiseの説明の前に、同期処理と非同期処理について確認しましょう。コードが上から下へと読み取られる同期処理に対し、非同期処理とは上から下へと順に読み取られない(上からのコードの完了実行を待たない)ことを指します。

非同期処理の簡単な例を見ていきます。

setTimeout(() => {
  console.log("a");
}, 1000);
setTimeout(() => {
  console.log("b");
}, 1000);
setTimeout(() => {
  console.log("c");
}, 1000);
setTimeout(() => {
  console.log("d");
}, 1000);
setTimeout(() => {
  console.log("e");
}, 1000);

同期処理の考えでは合計5秒(1000ms=1s)かかる計算です。しかしsetTimeoutは非同期API であるため非同期に処理が実行され、上から(eに対してa,b,c,d)のコードの実行完了を待たず1秒で処理が終了します。
では,上の例を非同期処理の中に同期処理があるようにコードを変更します。(aの実行を待ってからb,c,d,eが実行されるように)

setTimeout(() => {
  console.log("a");
  setTimeout(() => {
    console.log("b");
    setTimeout(() => {
      console.log("c");
      setTimeout(() => {
        console.log("d");
        setTimeout(() => {
          console.log("e");
        }, 1000);
      }, 1000);
    }, 1000);
  }, 1000);
}, 1000);

めちゃくちゃ読みにくいですね...いわゆるコールパック地獄です。これではコードの可読性が落ち、読みにくいコードです。

Promise

前置きが長くなりましたがコールバック地獄を解決するためにPromiseがあります。Promiseを用いることで非同期処理をより簡単に、コードの可読性が上がるようにしてくれます。
ひとまずコールバック地獄をPromiseで書き換えます。

new Promise((resolve) => {
  setTimeout(() => {
    console.log("a");
    resolve();
  }, 1000);
})
  .then(() => {
    return new Promise((resolve) => {
      setTimeout(() => {
        console.log("b");
        resolve();
      }, 1000);
    });
  })
  .then(() => {
    return new Promise((resolve) => {
      setTimeout(() => {
        console.log("c");
        resolve();
      }, 1000);
    });
  })
  .then(() => {
    return new Promise((resolve) => {
      setTimeout(() => {
        console.log("d");
        resolve();
      }, 1000);
    });
  })
  .then(() => {
    return new Promise((resolve) => {
      setTimeout(() => {
        console.log("e");
      }, 1000);
    });
  });

ただこれではかなり冗長なので、関数を用いて省略します。

function promiseFn(val) {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(val);
      resolve();
    }, 1000);
  });
}
promiseFn("a")
  .then(() => {
    return promiseFn("b");
  })
  .then(() => {
    return promiseFn("c");
  })
  .then(() => {
    return promiseFn("d");
  })
  .then(() => {
    return promiseFn("e");
  });

これでかなりスッキリと直感的で読みやすいコードになりました。


以下Promiseのポイントを挙げます。

①Promiseをインスタンス化し、引数はコールパック関数である。

new Promise(() => {});//コールバック関数は無名関数でも可。

②このコールバック関数の引数はsolve,rejectの2つの引数を取る。

new Promise((resolve,reject) => {});//使わないものは省略可能

③resolveが呼ばれた場合にはthenメソッドの中のコールバック関数が実行される。

new Promise((resolve, reject) => {
  resolve();//resolveを呼ぶ
}).then(() => {
  //同期処理or非同期処理
})

④thenメソッドのコールバック関数内が非同期処理の場合、Promiseのインスタンスを返す必要がある。(Promiseチェーン)

new Promise((resolve, reject) => {
  resolve();
}).then(() => {
  new Promise(() => {})
})

以上4点を踏まえて、コールバック地獄からPromiseへの書き換えを見直してもえらえると理解しやすいかもしれません。

終わりに

非同期の成功処理(esolveが呼ばれた場合にはthenメソッドの中のコールバック関数が実行される。)の反対に失敗処理(rejectが呼ばれた場合にはcatchメソッドの中のコールバック関数が実行される。)など、Promiseをより直感的に記述できるようにES8から導入されたAwait/Asyncなどがありますが、それらはまた後日この記事に追記していきます!