changed 6 years ago
Linked with GitHub

JavaScriptのasync/awaitをforEachで使ったらハマった話

Takahashi Yuki
2019-03-26 社内勉強会資料


Mさん「なんかawaitでとまらないんですけど……

ぼく「awaitがとまらないわけないじゃん?どれどれ


export function timer (delay) {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log(`timer ${delay}ms`)
      resolve()
    }, delay)
  })
}
import { timer } from './timer'

;[100, 300, 200].forEach(async delay => {
  await timer(delay)
})

期待した結果

$ timer 100ms
$ timer 300ms
$ timer 200ms

実際の結果

$ timer 100ms
$ timer 200ms
$ timer 300ms

ぼく「うーん、これはawaitされてない🤔


async/await

async/awaitはPromiseを同期的なコードのように記述することのできる糖衣構文


Promise

非同期処理を抽象化したオブジェクト


then で逐次処理

import { timer } from './timer'

let promiseChain = Promise.resolve()
;[100, 300, 200].forEach(delay => {
  promiseChain = promiseChain.then(() => timer(delay))
})


async/awaitでも
forofを使えば期待通りに逐次処理される

import { timer } from './timer'

;(async () => {
  for (const delay of [100, 300, 200]) {
    await timer(delay)
  }
})()

forEachの実装を簡略化したもの

Array.prototype.forEach = callback => {
  for (let index = 0; index < this.length; index++) {
    callback(this[index], index, this) // <-awaitしてない犯人
  }
}

awaitするforEach

import { timer } from './timer'

async function asyncForEach (array, callback) {
  for (let index = 0; index < array.length; index++) {
    await callback(array[index], index, array)
  }
}

asyncForEach([100, 300, 200], async delay => {
  await timer(delay)
})

forEachはわかった。ならmapは?


import { timer } from './timer'

const result = [100, 300, 200].map(async delay => await timer(delay))
console.log(result)

[ Promise { <pending> },
  Promise { <pending> },
  Promise { <pending> } ]
timer 100ms
timer 200ms
timer 300ms
Select a repo