Madogiwa Blog

主に技術系の学習メモに使っていきます。

JavaScript: Promiseについて分からなすぎたので手を動かしながら学んだことをMemo

JavaScriptのPromiseがあまりにも分からなかったので、手を動かしながらいろいろと動きを勉強したので、メモしておきます✍

Promiseの基本

Promiseとは、MDNを見てみると下記のような説明がされています。
非同期処理の実行の完了や失敗を検知することが出来るようなObjectみたいですね👀

Promise オブジェクトは非同期処理の最終的な完了処理(もしくは失敗)およびその結果の値を表現します。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise

MDNのサンプルコードが下記です。new Promise()でPromiseのオブジェクトを作成して、functionの引数のresolvethenで渡したcallback、rejectcatchで渡したcallbackが入ります。※thenが成功、catchが失敗した際に実行されます。

下記のサンプルでは、0.3秒後にcallbackに引数'foo'を渡して実行するPromiseのオブジェクトを作成して、 promise1.thenに引数の値を標準出力するfunctionをcallbackとして設定しているので、fooがコンソールに表示されます。

promise1自体を標準出力するとPromiseのオブジェクトが返却されます。

var promise1 = new Promise(function(resolve, reject) {
  setTimeout(function() {
    resolve('foo');
  }, 300);
});

promise1.then(function(value) {
  console.log(value);
  // expected output: "foo"
});

console.log(promise1);
// expected output: [object Promise]

いろいろ動かして試してみる。

まずは、検証用に下記のようなものを用意しました。
sleepは、timeで指定した時間後にcallbackを実行する遅延処理です。その他については普通に標準出力する処理です。

function sleep(time, callback) {
  setTimeout(() => { callback(null) }, time);
}
const exec_1 = () => { console.log('async 1 execute!!') }
const exec_2 = () => { console.log('async 2 execute!!') }
const exec_3 = () => { console.log('async 3 execute!!') }

下記のような処理を実行するとresultに書いたような結果になります。sleepで1秒まっているので、 exec_2よりもexec_3が先に実行されてしまっていますね。

exec_1()
sleep(1000, exec_2)
exec_3()

/* result
async 1 execute!!
async 3 execute!!
async 2 execute!!
*/

これをPromiseを使って、exec_2、exec_3の順番で実行されるようにしていこうと思います。

exec_2のあとにexec_3を実行するために遅延処理をPromiseにする。

まずは、sleepをPromiseのオブジェクトを返却するようにして、完了のタイミングで処理を実行出来るようにします。

function delay(time) {
  return new Promise((resolve) => {
    sleep(time, resolve)
  })
}

👇delaythenを使って処理修正したのが下記です。 delay(1000).then(exec_2)で1秒待ったあとにexec_2が実行されます。 thenの返り値もまたPromiseのオブジェクトになるので、then(exec_2).then(exec_3)によってexec_2実行後にexec_3が実行されます。

そのため結果は、resultのようになります。

exec_1()
delay(1000).then(exec_2).then(exec_3)
/* result
async 1 execute!!
// 1 sec
async 2 execute!!
async 3 execute!!
*/

thenのなかで更にPromiseを作成して各メソッドを1秒間隔で実行する

こうなってくると1秒間隔で各メソッドを実行したくなるわけですが、単純に下記のようにすると上手くいかないわけですよ。。

delay(1000).then().then(delay(1000).then(exec_3))

/* result
async 1 execute!!
// 2 sec
async 2 execute!!
async 3 execute!!
*/

なぜ上手く行かないかというと下記のような処理になってしまっていたわけですね。。。

  1. exec1が実行 -> async 1 execute!!が表示
  2. 1秒待つ
  3. exec2が実行予約(1秒後) -> delay(1000).then(exec_2)が実行
  4. exec3が実行予約(1秒後) -> delay(1000).then(exec_3)が実行
  5. 1秒待つ(exec_2、exec_3のdelayはほぼ同時に実行されるため)
  6. exec2が実行 -> async 2 execute!!が表示
  7. exec3が実行 -> async 2 execute!!が表示

上記を考慮して修正したコードが下記です。

exec_1()
delay(1000).then(
  () => {
    return new Promise(
      resolve => { exec_2(); delay(1000).then(resolve) }
    )
  }
).then(exec_3)

/* result
async 1 execute!!
// 1 sec
async 2 execute!!
// 1 sec
async 3 execute!!
*/
  1. exec1が実行 -> async 1 execute!!が表示
  2. 1sec待つ -> delay(1000)が実行
  3. exec_2と1秒後にcallbackを実行するPromiseが設定される -> delay(1000).then(..)が実行される
  4. thenが実行されてexec_2(); delay(1000).then(exec_3)が実行されるため、exec_2が実行されたあと1秒待ってexec_3が実行される。 -> exec_2(); delay(1000).then(resolve)が実行される

これでresultのように1秒間隔で実行される処理が実現出来ました🙌

おわりに

今回はJavaScriptのPromiseが分からなすぎたので、いろいろ実行しながら学んでみました。
やはりドキュメントを読むだけでなくて、手を動かすといろいろと理解が深まりますね。

それでは👋

参考

developer.mozilla.org

qiita.com