function batchAsync(fn, items, concurrency) {
  let batches = Promise.resolve([])
  items = [...items]

  while (items.length) {
    const slice = items.splice(0, concurrency)
    batches = batches.then((previousBatch) => {
      return Promise.all(slice.map((...args) => fn(...args))).then((currentBatch) => [
        ...previousBatch,
        ...currentBatch
      ])
    })
  }

  return batches
}
Run `fn` in parallel on #`concurrency` number of `items`, spread over #`items / concurrency` sequential batches
@private
@param {() => Promise} fn
@param {*[]} items
@param {number} concurrency
@returns {Promise<*[]>}