LoginSignup
5
2

More than 3 years have passed since last update.

並列処理数を指定できてキャンセルできる Promise.all の実装

Last updated at Posted at 2020-06-04

実装

import type { AbortSignal } from 'abort-controller'

// Node.js など DOMException がない場合に必要に応じて定義
export const DOMException =
  ('undefined' !== typeof window && window.DOMException) ||
  ('undefined' !== typeof self && self.DOMException) ||
    class DOMException extends Error {
      constructor(message: string, name: string) {
        super(message)
        this.name = name
      }
    }

// キャンセル機能が不要な場合、同時実行数のみを渡せるように
type Options = number | { concurrency: number; signal?: AbortSignal }

export const promiseAllWithConcurrencyLimit =
  async <T>(promises: (() => Promise<T>)[], options: Options) => {
    const opts = 'number' === typeof options ? { concurrency: options } : options
    const { concurrency, signal } = opts
    const results: T[] = []
    let idx = 0
    await Promise.all(Array.from({ length: concurrency }).map(async () => {
      while (true) {
        if (signal?.aborted) 
          return Promise.reject(new DOMException('aborted', 'AbortError'))
        const cur = idx++
        const task = promises[cur]
        if (!task) return
        results[cur] = await task()
      }
    }))
    return results
  }

記事を書くまでの経緯

以前、並列処理数を指定できる Promise.all の実装という記事を書きました

長時間かかるタスクの実行中になんらかの要因でやっぱり中止したいと思うかもしれません
fetch の仕様を参考にキャンセルできるようにしたのが上記になります

使用例

const controller = new AbortController()
const onCancelButtonClicked = () => {
  controller.abort()
}
try {
  await promiseAllWithConcurrencyLimit(
    userIds.map(id => async () => fetchUserById(id)),
    { concurrency: 3, signal: controller.signal }
  )
} catch (err) {
  if ('AbortError' !== err?.name) throw err
}
5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2