🌀

ES6 async/awaitできないときに振り返るパターン集

5 min read

概要

javascriptは処理を待たないで実行されるので、明示的に同期処理を書く必要があります。

非同期のままだと実行順序が(書いた自分でも)わかりにくいので、
なるべく同期処理で書きたいと思っています。

で、aync/awaitで書けばいいんだろ? ぐらいの気持ちで使ったところ、だいぶつまづきました。。
困ったときのためにソースコードを記載しておきます
※chrome developer toolのconsoleで実行できます。

パターン1からパターン4まであります。
結果がどうなるか予想してみてください。
ポイントは、「Promiseを返す関数を実行するときはawaitをつける」です。

元ネタ: https://qiita.com/kuroeda_0011/items/f559ee47708143cc7ab6#複数のループ

パターン1

// 即時関数
(function() {
  const sleep = async (ms) => new Promise(r => setTimeout(r, ms))
  const counts = Array(5)

  const runLoopOne = async () => {
      for (const c of counts) {
          console.log('loop 1')
      }
  }

  const runLoopTwo = async () => {
      for (const c of counts) {
          console.log('loop 2')
          await sleep(1)
      }
  }

  const runLoopThree = async () => {
      for await (const c of counts) {
          console.log('loop 3')
      }
  }

  const runLoops = async () => {
      runLoopOne()
      runLoopTwo()
      runLoopThree()
  }

  runLoops()

  console.log('finish function')
}())

結果

// loop 1
// loop 1
// loop 1
// loop 1
// loop 1
// loop 2
// finish function
// loop 3
// loop 3
// loop 3
// loop 3
// loop 3
// loop 2
// loop 2
// loop 2
// loop 2

パターン2

runLoopTwoを同期的に処理したい(loop 2finish functionより先に出力したい)

// 即時関数
(async function() { // asyncをつけた
  const sleep = async (ms) => new Promise(r => setTimeout(r, ms))
  const counts = Array(5)

  const runLoopOne = async () => {
    for (const c of counts) {
      console.log('loop 1')
    }
  }

  const runLoopTwo = async () => {
    // runLoopTwoを同期処理に変更した
    return new Promise(async resolve => {
      for (const c of counts) {
        console.log('loop 2')
        await sleep(1)
        console.log(new Date().getTime()) // sleepできているかの確認
      }
      resolve()
    })
  }

  const runLoopThree = async () => {
    for await (const c of counts) {
      console.log('loop 3')
    }
  }

  const runLoops = async () => {
    runLoopOne()
    await runLoopTwo() // awaitをつけた
    runLoopThree()
  }

  await runLoops() // awaitをつけた

  console.log('finish function')
}())

結果

// loop 1
// loop 1
// loop 1
// loop 1
// loop 1
// loop 2
// loop 2
// loop 2
// loop 2
// loop 2
// finish function
// loop 3
// loop 3
// loop 3
// loop 3
// loop 3

パターン3

runLoopTwoを同期的に処理したい(loop 3finish functionより先に出力したい)

// 即時関数
(async function() { // asyncをつけた
  const sleep = async (ms) => new Promise(r => setTimeout(r, ms))
  const counts = Array(5)

  const runLoopOne = async () => {
    for (const c of counts) {
      console.log('loop 1')
    }
  }

  const runLoopTwo = async () => {
    for (const c of counts) {
      console.log('loop 2')
      await sleep(1)
    }
  }

  const runLoopThree = async () => {
    for await (const c of counts) {
      console.log('loop 3')
    }
  }

  const runLoops = async () => {
    runLoopOne()
    runLoopTwo()
    await runLoopThree() // awaitをつけた
  }

  await runLoops() // awaitをつけた

  console.log('finish function')
}())

結果

// loop 1
// loop 1
// loop 1
// loop 1
// loop 1
// loop 2
// loop 3
// loop 3
// loop 3
// loop 3
// loop 3
// finish function
// loop 2
// loop 2
// loop 2
// loop 2

パターン4

runLoopTwoを同期的に処理したい(loop 2finish functionより先に出力したい)
runLoopThreeを同期的に処理したい(loop 3finish functionより先に出力したい)

// 即時関数
(async function() { // asyncをつけた
  const sleep = async (ms) => new Promise(r => setTimeout(r, ms))
  const counts = Array(5)

  const runLoopOne = async () => {
    for (const c of counts) {
      console.log('loop 1')
    }
  }

  const runLoopTwo = async () => {
    // runLoopTwoを同期処理に変更した
    return new Promise(async resolve => {
      for (const c of counts) {
        console.log('loop 2')
        await sleep(1)
        console.log(new Date().getTime())
      }
      resolve()
    })
  }

  const runLoopThree = async () => {
    for await (const c of counts) {
      console.log('loop 3')
    }
  }

  const runLoops = async () => {
    runLoopOne()
    await runLoopTwo() // awaitをつけた
    await runLoopThree() // awaitをつけた
  }

  await runLoops() // awaitをつけた

  console.log('finish function')
}())

結果

// loop 1
// loop 1
// loop 1
// loop 1
// loop 1
// loop 2
// loop 2
// loop 2
// loop 2
// loop 2
// loop 3
// loop 3
// loop 3
// loop 3
// loop 3
// finish function

解決しない点

パターン2の同期処理のために下記のようにnew Promise(async (resolve, reject) => {})と書くと、eslint的にはエラーとなるらしい
※ググっても簡単に説明している記事が見つからなかったので、妥協して使っています。

  const runLoopTwo = async () => {
    // runLoopTwoを同期処理に変更した
    return new Promise(async resolve => {
      for (const c of counts) {
        console.log('loop 2')
        await sleep(1)
        console.log(new Date().getTime()) // sleepできているかの確認
      }
      resolve()
    })
  }

https://eslint.org/docs/rules/no-async-promise-executor#rule-details
https://tyru.hatenablog.com/entry/2018/08/04/220530

参考

「非同期処理」「同期処理」「イベントループ」って何?:

https://coliss.com/articles/build-websites/operation/javascript/javascript-visualized-event-loop.html

https://qiita.com/l1lhu1hu1/items/57dcc7cb867eee951f36

Discussion

ログインするとコメントできます