p-mapで配列の配列の非同期処理のabort対応

配列の配列の非同期処理で対象API(以下ではsleep)がabortする場合のメモ

# 前回

https://qiita.com/kuangzhixitian3/items/e03756c9e49158221943

## 配列が１つの場合

### 全部同時実行

```plantuml
@startuml
robust "全体" as all
concise "https://test/1" as test1
compact concise "abort" as test1c
concise "https://test/2" as test2
compact concise "abort" as test2c
concise "https://test/3" as test3
concise "https://test/4" as test4
compact concise "abort" as test4c
concise "https://test/5" as test5
compact concise "abort" as test5c
concise "https://test/10" as test10

@all
0 is 待機中
+1 is 処理中
+0.01 is 中断中

@1
test1 -> test1c 
test2 -> test2c
test4 -> test4c
test5 -> test5c

@test1
1 is 処理中
+1 is {hidden}
@test1c
1 is aborted #gray
+0.01 is {hidden}

@test2
1 is 処理中 #pink
+2 is {hidden}
@test2c
1 is aborted #gray
+0.01 is {hidden}

@test3
1 is 処理中 #pink
+0.01 is {hidden}

@test4
1 is 処理中 #pink
+4 is {hidden}
@test4c
1 is aborted #gray
+0.01 is {hidden}

@test5
1 is 処理中
+5 is {hidden}
@test5c
1 is aborted #gray
+0.01 is {hidden}

@test10
1 is abort #gray
+0.01 is {hidden}

@enduml
```

```ts
import { describe, test } from 'vitest'
import pMap from 'p-map'

const sleep = (wait: number) =>
  new Promise((resolve, reject) => {
    const waitSeconds = wait / 1000
    if (waitSeconds % 3 === 0) {
      reject('Sleep NG before wait')
    } else if (waitSeconds % 2 === 0) {
      setTimeout(() => reject(`${wait} - Sleep NG after wait`), wait)
    } else {
      setTimeout(() => resolve(wait), wait)
    }
  })

function checkLog(text: string) {
  console.log(`time : ${new Date().toISOString()} - text: ${text}`)
}

const testOptions = {
  timeout: 1000 * 100
}

function getIndexOrNgMessage(
  abortAction: Function,
  site: string,
  getIndexAction: (site: string) => string
) {
  const index = getIndexAction(site)
  if (Number.isNaN(Number(index))) {
    checkLog(`${site} - abort - index is NaN`)
    abortAction()
    return `${site} - abort - index is NaN`
  }
  if (site[site.length - 2] !== '/') {
    checkLog(`${site} - abort - index >= 10`)
    abortAction()
    return `${site} - abort - index >= 10`
  }

  return index
}

function checkIndex(abortAction: Function, site: string, topIndex?: number, lastIndex?: number) {
  if (topIndex && lastIndex && topIndex + lastIndex >= 10) {
    checkLog(`${site} - abort - (topIndex + lastIndex) >= 10`)
    abortAction()
    return `${site} - abort - (topIndex + lastIndex) >= 10`
  }

  return 'OK'
}

const mapper1 = (abortController: AbortController, topIndex?: number) => async (site: string) => {
  const abortEvent = () => {
    checkLog(`${site} - aborted - from event`)
  }
  abortController.signal.addEventListener('abort', abortEvent)
  function removeEvent() {
    abortController.signal.removeEventListener('abort', abortEvent)
  }
  const abortAction = () => {
    removeEvent()
    abortController.abort()
  }

  if (abortController.signal.aborted) {
    checkLog(`${site} - aborted`)
    removeEvent()
    return `${site} - aborted`
  }

  const indexOrNgMessage = getIndexOrNgMessage(abortAction, site, (site) => site.slice(-1))
  if (Number.isNaN(Number(indexOrNgMessage))) {
    removeEvent()
    return indexOrNgMessage
  }

  const indexCheck = checkIndex(abortAction, site, topIndex, Number(indexOrNgMessage))
  if (indexCheck !== 'OK') {
    removeEvent()
    return indexCheck
  }

  const wait = Number(indexOrNgMessage) * 1000
  try {
    const _wait = await sleep(wait)
    checkLog(site)
    removeEvent()
    return `${site} - ok - ${_wait}`
  } catch (error) {
    checkLog(`${site} - ng - ${error}`)
    removeEvent()
    return `${site} - ng - ${error}`
  }
}

describe('p-map 非同期処理１つの配列', () => {
  test('使い方１ー全部同時実行', testOptions, async () => {
    const sites = [
      'https://test/1',
      'https://test/2',
      'https://test/3',
      'https://test/4',
      'https://test/5',
      'https://test/10'
    ]

    const abortController = new AbortController()

    checkLog('[start]')

    try {
      const result = await pMap(sites, mapper1(abortController), { signal: abortController.signal })
      console.log(result)
    } catch (error) {
      console.log(error)
    }
  })
})
```

実行結果
```ts
time : 2025-01-26T10:39:49.323Z - text: [start]
time : 2025-01-26T10:39:49.325Z - text: https://test/3 - ng - Sleep NG before wait
time : 2025-01-26T10:39:49.326Z - text: https://test/10 - abort - index >= 10
time : 2025-01-26T10:39:49.326Z - text: https://test/1 - aborted - from event
time : 2025-01-26T10:39:49.326Z - text: https://test/2 - aborted - from event
time : 2025-01-26T10:39:49.326Z - text: https://test/4 - aborted - from event
time : 2025-01-26T10:39:49.327Z - text: https://test/5 - aborted - from event
DOMException {}
```

実行結果（※中断データを除外した場合）
```ts
time : 2025-01-26T10:38:54.874Z - text: [start]
time : 2025-01-26T10:38:54.876Z - text: https://test/3 - ng - Sleep NG before wait
time : 2025-01-26T10:38:55.903Z - text: https://test/1
time : 2025-01-26T10:38:56.887Z - text: https://test/2 - ng - 2000 - Sleep NG after wait
time : 2025-01-26T10:38:58.887Z - text: https://test/4 - ng - 4000 - Sleep NG after wait
time : 2025-01-26T10:38:59.879Z - text: https://test/5
[
  'https://test/1 - ok - 1000',
  'https://test/2 - ng - 2000 - Sleep NG after wait',
  'https://test/3 - ng - Sleep NG before wait',
  'https://test/4 - ng - 4000 - Sleep NG after wait',
  'https://test/5 - ok - 5000'
]
```


### 最大２個まで同時実行

```plantuml
@startuml
robust "全体" as all
concise "https://test/1" as test1
concise "https://test/2" as test2
concise "https://test/3" as test3
concise "https://test/4" as test4
concise "https://test/5" as test5
compact concise "abort" as test5c
concise "https://test/10" as test10

@all
0 is 待機中
+1 is 処理中
+5 is 中断中

@6
test5 -> test5c

@test1
1 is 処理中
+1 is {hidden}

@test2
1 is 処理中 #pink
+2 is {hidden}

@test3
2 is 処理中 #pink
+0.01 is {hidden}

@test4
2 is 処理中 #pink
+4 is {hidden}

@test5
3 is 処理中
+5 is {hidden}
@test5c
6 is aborted #gray
+0.01 is {hidden}

@test10
6 is abort #gray
+0.01 is {hidden}

@enduml
```

```ts
import { describe, test } from 'vitest'
import pMap from 'p-map'

const sleep = (wait: number) =>
  new Promise((resolve, reject) => {
    const waitSeconds = wait / 1000
    if (waitSeconds % 3 === 0) {
      reject('Sleep NG before wait')
    } else if (waitSeconds % 2 === 0) {
      setTimeout(() => reject(`${wait} - Sleep NG after wait`), wait)
    } else {
      setTimeout(() => resolve(wait), wait)
    }
  })

function checkLog(text: string) {
  console.log(`time : ${new Date().toISOString()} - text: ${text}`)
}

const testOptions = {
  timeout: 1000 * 100
}

function getIndexOrNgMessage(
  abortAction: Function,
  site: string,
  getIndexAction: (site: string) => string
) {
  const index = getIndexAction(site)
  if (Number.isNaN(Number(index))) {
    checkLog(`${site} - abort - index is NaN`)
    abortAction()
    return `${site} - abort - index is NaN`
  }
  if (site[site.length - 2] !== '/') {
    checkLog(`${site} - abort - index >= 10`)
    abortAction()
    return `${site} - abort - index >= 10`
  }

  return index
}

function checkIndex(abortAction: Function, site: string, topIndex?: number, lastIndex?: number) {
  if (topIndex && lastIndex && topIndex + lastIndex >= 10) {
    checkLog(`${site} - abort - (topIndex + lastIndex) >= 10`)
    abortAction()
    return `${site} - abort - (topIndex + lastIndex) >= 10`
  }

  return 'OK'
}

const mapper1 = (abortController: AbortController, topIndex?: number) => async (site: string) => {
  const abortEvent = () => {
    checkLog(`${site} - aborted - from event`)
  }
  abortController.signal.addEventListener('abort', abortEvent)
  function removeEvent() {
    abortController.signal.removeEventListener('abort', abortEvent)
  }
  const abortAction = () => {
    removeEvent()
    abortController.abort()
  }

  if (abortController.signal.aborted) {
    checkLog(`${site} - aborted`)
    removeEvent()
    return `${site} - aborted`
  }

  const indexOrNgMessage = getIndexOrNgMessage(abortAction, site, (site) => site.slice(-1))
  if (Number.isNaN(Number(indexOrNgMessage))) {
    removeEvent()
    return indexOrNgMessage
  }

  const indexCheck = checkIndex(abortAction, site, topIndex, Number(indexOrNgMessage))
  if (indexCheck !== 'OK') {
    removeEvent()
    return indexCheck
  }

  const wait = Number(indexOrNgMessage) * 1000
  try {
    const _wait = await sleep(wait)
    checkLog(site)
    removeEvent()
    return `${site} - ok - ${_wait}`
  } catch (error) {
    checkLog(`${site} - ng - ${error}`)
    removeEvent()
    return `${site} - ng - ${error}`
  }
}

describe('p-map 非同期処理１つの配列', () => {
  test('使い方２ー最大２個まで同時実行', testOptions, async () => {
    const sites = [
      'https://test/1',
      'https://test/2',
      'https://test/3',
      'https://test/4',
      'https://test/5',
      'https://test/10'
    ]

    const abortController = new AbortController()
    const concurrency = 2

    checkLog('[start]')

    try {
      const result = await pMap(sites, mapper1(abortController), {
        concurrency,
        signal: abortController.signal
      })
      console.log(result)
    } catch (error) {
      console.log(error)
    }
  })
})
```

実行結果
```ts
time : 2025-01-26T10:50:46.228Z - text: [start]
time : 2025-01-26T10:50:47.242Z - text: https://test/1
time : 2025-01-26T10:50:47.244Z - text: https://test/3 - ng - Sleep NG before wait
time : 2025-01-26T10:50:48.235Z - text: https://test/2 - ng - 2000 - Sleep NG after wait
time : 2025-01-26T10:50:51.245Z - text: https://test/4 - ng - 4000 - Sleep NG after wait
time : 2025-01-26T10:50:51.247Z - text: https://test/10 - abort - index >= 10
time : 2025-01-26T10:50:51.250Z - text: https://test/5 - aborted - from event
DOMException {}
```

実行結果（※中断データを除外した場合）
```ts
time : 2025-01-26T10:51:50.045Z - text: [start]
time : 2025-01-26T10:51:51.059Z - text: https://test/1
time : 2025-01-26T10:51:51.060Z - text: https://test/3 - ng - Sleep NG before wait
time : 2025-01-26T10:51:52.060Z - text: https://test/2 - ng - 2000 - Sleep NG after wait
time : 2025-01-26T10:51:55.075Z - text: https://test/4 - ng - 4000 - Sleep NG after wait
time : 2025-01-26T10:51:57.074Z - text: https://test/5
[
  'https://test/1 - ok - 1000',
  'https://test/2 - ng - 2000 - Sleep NG after wait',
  'https://test/3 - ng - Sleep NG before wait',
  'https://test/4 - ng - 4000 - Sleep NG after wait',
  'https://test/5 - ok - 5000'
]
```


## 配列が２つの場合（シンプル）

### 全部同時実行からの全部同時実行

```plantuml
@startuml
robust "全体" as all
concise "https://test/7" as test7
concise "https://test/7/1" as test7_1
compact concise "abort" as test71c
concise "https://test/7/2" as test7_2
compact concise "abort" as test72c
concise "https://test/7/3" as test7_3
concise "https://test/7/4" as test7_4
compact concise "abort" as test74c
concise "https://test/7/5" as test7_5
compact concise "abort" as test75c
concise "https://test/7/6" as test7_6
compact concise "abort" as test76c
concise "https://test/7/7" as test7_7
compact concise "abort" as test77c

@all
0 is 待機中
+1 is 処理中
+7 is 中断中

@8
test7_1 -> test71c
test7_2 -> test72c
test7_4 -> test74c
test7_5 -> test75c
test7_6 -> test76c
test7_7 -> test77c

@test7
1 is 処理中
+7 is {hidden}

@test7_1
8 is 処理中
+1 is {hidden}
@test71c
8 is aborted #gray
+0.01 is {hidden}

@test7_2
8 is 処理中 #pink
+2 is {hidden}
@test72c
8 is aborted #gray
+0.01 is {hidden}

@test7_3
8 is abort #gray
+0.01 is {hidden}

@test7_4
8 is abort #gray
+0.01 is {hidden}
@test74c
8 is aborted #gray
+0.01 is {hidden}

@test7_5
8 is abort #gray
+0.01 is {hidden}
@test75c
8 is aborted #gray
+0.01 is {hidden}

@test7_6
8 is abort #gray
+0.01 is {hidden}
@test76c
8 is aborted #gray
+0.01 is {hidden}

@test7_7
8 is abort #gray
+0.01 is {hidden}
@test77c
8 is aborted #gray
+0.01 is {hidden}

@enduml
```

```ts
import { describe, test } from 'vitest'
import pMap from 'p-map'

const sleep = (wait: number) =>
  new Promise((resolve, reject) => {
    const waitSeconds = wait / 1000
    if (waitSeconds % 3 === 0) {
      reject('Sleep NG before wait')
    } else if (waitSeconds % 2 === 0) {
      setTimeout(() => reject(`${wait} - Sleep NG after wait`), wait)
    } else {
      setTimeout(() => resolve(wait), wait)
    }
  })

function checkLog(text: string) {
  console.log(`time : ${new Date().toISOString()} - text: ${text}`)
}

const testOptions = {
  timeout: 1000 * 100
}

function getIndexOrNgMessage(
  abortAction: Function,
  site: string,
  getIndexAction: (site: string) => string
) {
  const index = getIndexAction(site)
  if (Number.isNaN(Number(index))) {
    checkLog(`${site} - abort - index is NaN`)
    abortAction()
    return `${site} - abort - index is NaN`
  }
  if (site[site.length - 2] !== '/') {
    checkLog(`${site} - abort - index >= 10`)
    abortAction()
    return `${site} - abort - index >= 10`
  }

  return index
}

function checkIndex(abortAction: Function, site: string, topIndex?: number, lastIndex?: number) {
  if (topIndex && lastIndex && topIndex + lastIndex >= 10) {
    checkLog(`${site} - abort - (topIndex + lastIndex) >= 10`)
    abortAction()
    return `${site} - abort - (topIndex + lastIndex) >= 10`
  }

  return 'OK'
}

const mapper1 = (abortController: AbortController, topIndex?: number) => async (site: string) => {
  const abortEvent = () => {
    checkLog(`${site} - aborted - from event`)
  }
  abortController.signal.addEventListener('abort', abortEvent)
  function removeEvent() {
    abortController.signal.removeEventListener('abort', abortEvent)
  }
  const abortAction = () => {
    removeEvent()
    abortController.abort()
  }

  if (abortController.signal.aborted) {
    checkLog(`${site} - aborted`)
    removeEvent()
    return `${site} - aborted`
  }

  const indexOrNgMessage = getIndexOrNgMessage(abortAction, site, (site) => site.slice(-1))
  if (Number.isNaN(Number(indexOrNgMessage))) {
    removeEvent()
    return indexOrNgMessage
  }

  const indexCheck = checkIndex(abortAction, site, topIndex, Number(indexOrNgMessage))
  if (indexCheck !== 'OK') {
    removeEvent()
    return indexCheck
  }

  const wait = Number(indexOrNgMessage) * 1000
  try {
    const _wait = await sleep(wait)
    checkLog(site)
    removeEvent()
    return `${site} - ok - ${_wait}`
  } catch (error) {
    checkLog(`${site} - ng - ${error}`)
    removeEvent()
    return `${site} - ng - ${error}`
  }
}
function getWaitFromMapper1Message(mapper1Message: string) {
  const mapper1OkWaitString = mapper1Message.substring(mapper1Message.indexOf('ok') + 5)
  const mapper1OkWait = Number(mapper1OkWaitString)
  if (isNaN(mapper1OkWait)) {
    const mapper1NgWaitString = mapper1Message.substring(mapper1Message.indexOf('ng') + 5)
    const mapper1NgWaitStringWithoutErrorMessage = mapper1NgWaitString.substring(
      0,
      mapper1NgWaitString.indexOf('-')
    )
    const mapper1NgWait = Number(mapper1NgWaitStringWithoutErrorMessage)
    return isNaN(mapper1NgWait) ? 0 : mapper1NgWait
  } else {
    return mapper1OkWait
  }
}

const mapper2_max = (abortController: AbortController) => async (sites: string[]) => {
  const site = sites[0]
  const indexOrNgMessage = getIndexOrNgMessage(abortController.abort, site, (site) =>
    site.substring(site.length - 3, site.length - 2)
  )

  if (abortController.signal.aborted) {
    checkLog(`mapper2-index = ${indexOrNgMessage} - aborted`)
    return `mapper2-index = ${indexOrNgMessage} - aborted`
  }

  const abortEvent = () => {
    checkLog(`mapper2-index = ${indexOrNgMessage} - aborted - from event`)
  }
  abortController.signal.addEventListener('abort', abortEvent)
  function removeEvent() {
    abortController.signal.removeEventListener('abort', abortEvent)
  }

  if (Number.isNaN(Number(indexOrNgMessage))) return indexOrNgMessage

  const wait = Number(indexOrNgMessage) * 1000
  try {
    const _wait = (await sleep(wait)) as number
    checkLog(`mapper2-index = ${indexOrNgMessage}`)
    const result = await pMap(sites, mapper1(abortController, Number(indexOrNgMessage)))
    const waitResults = result.map(getWaitFromMapper1Message)
    const maxWait = Math.max(...waitResults)
    removeEvent()
    return `${site.substring(0, site.length - 2)} - ok - ${_wait + maxWait}`
  } catch (error) {
    checkLog(`mapper2-index = ${indexOrNgMessage} - ng - ${error}`)
    removeEvent()
    return `${site.substring(0, site.length - 2)} - ng - ${error}`
  }
}

describe('p-map 非同期処理２つの配列', () => {
  test('使い方１ー全部同時実行からの全部同時実行', testOptions, async () => {
    const sites = [
      // ['https://test/1/1'],
      // ['https://test/2/1', 'https://test/2/2']
      //   ['https://test/3/1', 'https://test/3/2', 'https://test/3/3']
      // ['https://test/4/1', 'https://test/4/2', 'https://test/4/3', 'https://test/4/4'],
      // [
      //   'https://test/5/1',
      //   'https://test/5/2',
      //   'https://test/5/3',
      //   'https://test/5/4',
      //   'https://test/5/5'
      // ]
      [
        'https://test/7/1',
        'https://test/7/2',
        'https://test/7/3',
        'https://test/7/4',
        'https://test/7/5',
        'https://test/7/6',
        'https://test/7/7'
      ]
    ]

    const abortController = new AbortController()

    checkLog('[start]')

    try {
      const result = await pMap(sites, mapper2_max(abortController), {
        signal: abortController.signal
      })
      console.log(result)
    } catch (error) {
      console.log(error)
    }
  })
})
```

実行結果
```ts
time : 2025-01-26T10:58:46.631Z - text: [start]
time : 2025-01-26T10:58:53.644Z - text: mapper2-index = 7
time : 2025-01-26T10:58:53.646Z - text: https://test/7/3 - abort - (topIndex + lastIndex) >= 10
time : 2025-01-26T10:58:53.651Z - text: mapper2-index = 7 - aborted - from event
time : 2025-01-26T10:58:53.651Z - text: https://test/7/1 - aborted - from event
time : 2025-01-26T10:58:53.652Z - text: https://test/7/2 - aborted - from event
DOMException {}
time : 2025-01-26T10:58:53.657Z - text: https://test/7/4 - aborted
time : 2025-01-26T10:58:53.658Z - text: https://test/7/5 - aborted
time : 2025-01-26T10:58:53.659Z - text: https://test/7/6 - aborted
time : 2025-01-26T10:58:53.660Z - text: https://test/7/7 - aborted
```

実行結果（※中断データを除外した場合）
```ts
time : 2025-01-26T10:59:32.801Z - text: [start]
time : 2025-01-26T10:59:39.813Z - text: mapper2-index = 7
time : 2025-01-26T10:59:40.827Z - text: https://test/7/1
time : 2025-01-26T10:59:41.831Z - text: https://test/7/2 - ng - 2000 - Sleep NG after wait
[ 'https://test/7 - ok - 9000' ]
```


### 全部同時実行からの直列実行

```plantuml
@startuml
robust "全体" as all
concise "https://test/7" as test7
concise "https://test/7/1" as test7_1
concise "https://test/7/2" as test7_2
concise "https://test/7/3" as test7_3
concise "https://test/7/4" as test7_4
compact concise "abort" as test74c
concise "https://test/7/5" as test7_5
compact concise "abort" as test75c
concise "https://test/7/6" as test7_6
compact concise "abort" as test76c
concise "https://test/7/7" as test7_7
compact concise "abort" as test77c

@11
test7_4 -> test74c
test7_5 -> test75c
test7_6 -> test76c
test7_7 -> test77c

@all
0 is 待機中
+1 is 処理中
+10 is 中断中

@test7
1 is 処理中
+7 is {hidden}

@test7_1
8 is 処理中
+1 is {hidden}

@test7_2
9 is 処理中 #pink
+2 is {hidden}

@test7_3
11 is abort #gray
+0.01 is {hidden}

@test7_4
11 is abort #gray
+0.01 is {hidden}
@test74c
11 is aborted #gray
+0.01 is {hidden}

@test7_5
11 is abort #gray
+0.01 is {hidden}
@test75c
11 is aborted #gray
+0.01 is {hidden}

@test7_6
11 is abort #gray
+0.01 is {hidden}
@test76c
11 is aborted #gray
+0.01 is {hidden}

@test7_7
11 is abort #gray
+0.01 is {hidden}
@test77c
11 is aborted #gray
+0.01 is {hidden}

@enduml
```

```ts
import { describe, test } from 'vitest'
import pMap from 'p-map'

const sleep = (wait: number) =>
  new Promise((resolve, reject) => {
    const waitSeconds = wait / 1000
    if (waitSeconds % 3 === 0) {
      reject('Sleep NG before wait')
    } else if (waitSeconds % 2 === 0) {
      setTimeout(() => reject(`${wait} - Sleep NG after wait`), wait)
    } else {
      setTimeout(() => resolve(wait), wait)
    }
  })

function checkLog(text: string) {
  console.log(`time : ${new Date().toISOString()} - text: ${text}`)
}

const testOptions = {
  timeout: 1000 * 100
}

function sum(a: number, b: number) {
  return a + b
}

function getIndexOrNgMessage(
  abortAction: Function,
  site: string,
  getIndexAction: (site: string) => string
) {
  const index = getIndexAction(site)
  if (Number.isNaN(Number(index))) {
    checkLog(`${site} - abort - index is NaN`)
    abortAction()
    return `${site} - abort - index is NaN`
  }
  if (site[site.length - 2] !== '/') {
    checkLog(`${site} - abort - index >= 10`)
    abortAction()
    return `${site} - abort - index >= 10`
  }

  return index
}

function checkIndex(abortAction: Function, site: string, topIndex?: number, lastIndex?: number) {
  if (topIndex && lastIndex && topIndex + lastIndex >= 10) {
    checkLog(`${site} - abort - (topIndex + lastIndex) >= 10`)
    abortAction()
    return `${site} - abort - (topIndex + lastIndex) >= 10`
  }

  return 'OK'
}

const mapper1 = (abortController: AbortController, topIndex?: number) => async (site: string) => {
  const abortEvent = () => {
    checkLog(`${site} - aborted - from event`)
  }
  abortController.signal.addEventListener('abort', abortEvent)
  function removeEvent() {
    abortController.signal.removeEventListener('abort', abortEvent)
  }
  const abortAction = () => {
    removeEvent()
    abortController.abort()
  }

  if (abortController.signal.aborted) {
    checkLog(`${site} - aborted`)
    removeEvent()
    return `${site} - aborted`
  }

  const indexOrNgMessage = getIndexOrNgMessage(abortAction, site, (site) => site.slice(-1))
  if (Number.isNaN(Number(indexOrNgMessage))) {
    removeEvent()
    return indexOrNgMessage
  }

  const indexCheck = checkIndex(abortAction, site, topIndex, Number(indexOrNgMessage))
  if (indexCheck !== 'OK') {
    removeEvent()
    return indexCheck
  }

  const wait = Number(indexOrNgMessage) * 1000
  try {
    const _wait = await sleep(wait)
    checkLog(site)
    removeEvent()
    return `${site} - ok - ${_wait}`
  } catch (error) {
    checkLog(`${site} - ng - ${error}`)
    removeEvent()
    return `${site} - ng - ${error}`
  }
}
function getWaitFromMapper1Message(mapper1Message: string) {
  const mapper1OkWaitString = mapper1Message.substring(mapper1Message.indexOf('ok') + 5)
  const mapper1OkWait = Number(mapper1OkWaitString)
  if (isNaN(mapper1OkWait)) {
    const mapper1NgWaitString = mapper1Message.substring(mapper1Message.indexOf('ng') + 5)
    const mapper1NgWaitStringWithoutErrorMessage = mapper1NgWaitString.substring(
      0,
      mapper1NgWaitString.indexOf('-')
    )
    const mapper1NgWait = Number(mapper1NgWaitStringWithoutErrorMessage)
    return isNaN(mapper1NgWait) ? 0 : mapper1NgWait
  } else {
    return mapper1OkWait
  }
}

const mapper2_sum = (abortController: AbortController) => async (sites: string[]) => {
  const site = sites[0]
  const indexOrNgMessage = getIndexOrNgMessage(abortController.abort, site, (site) =>
    site.substring(site.length - 3, site.length - 2)
  )

  if (abortController.signal.aborted) {
    checkLog(`mapper2-index = ${indexOrNgMessage} - aborted`)
    return `mapper2-index = ${indexOrNgMessage} - aborted`
  }

  const abortEvent = () => {
    checkLog(`mapper2-index = ${indexOrNgMessage} - aborted - from event`)
  }
  abortController.signal.addEventListener('abort', abortEvent)
  function removeEvent() {
    abortController.signal.removeEventListener('abort', abortEvent)
  }

  if (Number.isNaN(Number(indexOrNgMessage))) return indexOrNgMessage

  const wait = Number(indexOrNgMessage) * 1000
  try {
    const _wait = (await sleep(wait)) as number
    checkLog(`mapper2-index = ${indexOrNgMessage}`)
    const concurrency = 1
    const result = await pMap(sites, mapper1(abortController, Number(indexOrNgMessage)), {
      concurrency
    })
    const waitResults = result.map(getWaitFromMapper1Message)
    const sumWait = waitResults.reduce(sum, 0)
    removeEvent()
    return `${site.substring(0, site.length - 2)} - ${_wait + sumWait}`
  } catch (error) {
    checkLog(`mapper2-index = ${indexOrNgMessage} - ng - ${error}`)
    removeEvent()
    return `${site.substring(0, site.length - 2)} - ng - ${error}`
  }
}

describe('p-map 非同期処理２つの配列', () => {
  test('使い方３ー全部同時実行からの直列実行', testOptions, async () => {
    const sites = [
      // ['https://test/1/1'],
      // ['https://test/2/1', 'https://test/2/2']
      //   ['https://test/3/1', 'https://test/3/2', 'https://test/3/3']
      // ['https://test/4/1', 'https://test/4/2', 'https://test/4/3', 'https://test/4/4'],
      // [
      //   'https://test/5/1',
      //   'https://test/5/2',
      //   'https://test/5/3',
      //   'https://test/5/4',
      //   'https://test/5/5'
      // ]
      [
        'https://test/7/1',
        'https://test/7/2',
        'https://test/7/3',
        'https://test/7/4',
        'https://test/7/5',
        'https://test/7/6',
        'https://test/7/7'
      ]
    ]

    const abortController = new AbortController()

    checkLog('[start]')

    try {
      const result = await pMap(sites, mapper2_sum(abortController), {
        signal: abortController.signal
      })
      console.log(result)
    } catch (error) {
      console.log(error)
    }
  })
})
```

実行結果
```ts
time : 2025-01-26T11:09:44.271Z - text: [start]
time : 2025-01-26T11:09:51.294Z - text: mapper2-index = 7
time : 2025-01-26T11:09:52.296Z - text: https://test/7/1
time : 2025-01-26T11:09:54.300Z - text: https://test/7/2 - ng - 2000 - Sleep NG after wait
time : 2025-01-26T11:09:54.300Z - text: https://test/7/3 - abort - (topIndex + lastIndex) >= 10
time : 2025-01-26T11:09:54.301Z - text: mapper2-index = 7 - aborted - from event
DOMException {}
time : 2025-01-26T11:09:54.302Z - text: https://test/7/4 - aborted
time : 2025-01-26T11:09:54.303Z - text: https://test/7/5 - aborted
time : 2025-01-26T11:09:54.303Z - text: https://test/7/6 - aborted
time : 2025-01-26T11:09:54.303Z - text: https://test/7/7 - aborted
```

実行結果（※中断データを除外した場合）
```ts
time : 2025-01-26T11:10:13.635Z - text: [start]
time : 2025-01-26T11:10:20.647Z - text: mapper2-index = 7
time : 2025-01-26T11:10:21.659Z - text: https://test/7/1
time : 2025-01-26T11:10:23.665Z - text: https://test/7/2 - ng - 2000 - Sleep NG after wait
[ 'https://test/7 - 10000' ]
```



### 全部同時実行からの最大２個まで同時実行

```plantuml
@startuml
robust "全体" as all
concise "https://test/7" as test7
concise "https://test/7/1" as test7_1
concise "https://test/7/2" as test7_2
compact concise "abort" as test72c
concise "https://test/7/3" as test7_3
concise "https://test/7/4" as test7_4
compact concise "abort" as test74c
concise "https://test/7/5" as test7_5
compact concise "abort" as test75c
concise "https://test/7/6" as test7_6
compact concise "abort" as test76c
concise "https://test/7/7" as test7_7
compact concise "abort" as test77c

@9
test7_2 -> test72c
test7_4 -> test74c
test7_5 -> test75c
test7_6 -> test76c
test7_7 -> test77c

@all
0 is 待機中
+1 is 処理中
+8 is 中断中

@test7
1 is 処理中
+7 is {hidden}

@test7_1
8 is 処理中
+1 is {hidden}

@test7_2
8 is 処理中 #pink
+2 is {hidden}
@test72c
9 is aborted #gray
+0.01 is {hidden}

@test7_3
9 is abort #gray
+0.01 is {hidden}

@test7_4
9 is abort #gray
+0.01 is {hidden}
@test74c
9 is aborted #gray
+0.01 is {hidden}

@test7_5
9 is abort #gray
+0.01 is {hidden}
@test75c
9 is aborted #gray
+0.01 is {hidden}

@test7_6
9 is abort #gray
+0.01 is {hidden}
@test76c
9 is aborted #gray
+0.01 is {hidden}

@test7_7
9 is abort #gray
+0.01 is {hidden}
@test77c
9 is aborted #gray
+0.01 is {hidden}

@enduml
```

```ts
import { describe, test } from 'vitest'
import pMap from 'p-map'

const sleep = (wait: number) =>
  new Promise((resolve, reject) => {
    const waitSeconds = wait / 1000
    if (waitSeconds % 3 === 0) {
      reject('Sleep NG before wait')
    } else if (waitSeconds % 2 === 0) {
      setTimeout(() => reject(`${wait} - Sleep NG after wait`), wait)
    } else {
      setTimeout(() => resolve(wait), wait)
    }
  })

function checkLog(text: string) {
  console.log(`time : ${new Date().toISOString()} - text: ${text}`)
}

const testOptions = {
  timeout: 1000 * 100
}

function array_slice_2(acc: number[][], cur: number) {
  const topLengt = acc.length
  if (topLengt == 0) {
    acc.push([cur])
  } else if (acc[topLengt - 1].length == 2) {
    acc.push([cur])
  } else {
    acc[topLengt - 1].push(cur)
  }

  return acc
}

function array_sum_slice_2(acc: number[], cur: number[]) {
  const accLengt = acc.length
  if (accLengt == 0) {
    acc.push(...cur)
  } else {
    const curLength = cur.length
    const min = Math.min(acc[0], acc[1])
    const minIndex = acc[0] == min ? 0 : 1
    acc[minIndex] += cur[0]

    if (curLength == 2) {
      const min2 = Math.min(acc[0], acc[1])
      const min2Index = acc[0] == min2 ? 0 : 1
      acc[min2Index] += cur[1]
    }
  }

  return acc
}

function getIndexOrNgMessage(
  abortAction: Function,
  site: string,
  getIndexAction: (site: string) => string
) {
  const index = getIndexAction(site)
  if (Number.isNaN(Number(index))) {
    checkLog(`${site} - abort - index is NaN`)
    abortAction()
    return `${site} - abort - index is NaN`
  }
  if (site[site.length - 2] !== '/') {
    checkLog(`${site} - abort - index >= 10`)
    abortAction()
    return `${site} - abort - index >= 10`
  }

  return index
}

function checkIndex(abortAction: Function, site: string, topIndex?: number, lastIndex?: number) {
  if (topIndex && lastIndex && topIndex + lastIndex >= 10) {
    checkLog(`${site} - abort - (topIndex + lastIndex) >= 10`)
    abortAction()
    return `${site} - abort - (topIndex + lastIndex) >= 10`
  }

  return 'OK'
}

const mapper1 = (abortController: AbortController, topIndex?: number) => async (site: string) => {
  const abortEvent = () => {
    checkLog(`${site} - aborted - from event`)
  }
  abortController.signal.addEventListener('abort', abortEvent)
  function removeEvent() {
    abortController.signal.removeEventListener('abort', abortEvent)
  }
  const abortAction = () => {
    removeEvent()
    abortController.abort()
  }

  if (abortController.signal.aborted) {
    checkLog(`${site} - aborted`)
    removeEvent()
    return `${site} - aborted`
  }

  const indexOrNgMessage = getIndexOrNgMessage(abortAction, site, (site) => site.slice(-1))
  if (Number.isNaN(Number(indexOrNgMessage))) {
    removeEvent()
    return indexOrNgMessage
  }

  const indexCheck = checkIndex(abortAction, site, topIndex, Number(indexOrNgMessage))
  if (indexCheck !== 'OK') {
    removeEvent()
    return indexCheck
  }

  const wait = Number(indexOrNgMessage) * 1000
  try {
    const _wait = await sleep(wait)
    checkLog(site)
    removeEvent()
    return `${site} - ok - ${_wait}`
  } catch (error) {
    checkLog(`${site} - ng - ${error}`)
    removeEvent()
    return `${site} - ng - ${error}`
  }
}
function getWaitFromMapper1Message(mapper1Message: string) {
  const mapper1OkWaitString = mapper1Message.substring(mapper1Message.indexOf('ok') + 5)
  const mapper1OkWait = Number(mapper1OkWaitString)
  if (isNaN(mapper1OkWait)) {
    const mapper1NgWaitString = mapper1Message.substring(mapper1Message.indexOf('ng') + 5)
    const mapper1NgWaitStringWithoutErrorMessage = mapper1NgWaitString.substring(
      0,
      mapper1NgWaitString.indexOf('-')
    )
    const mapper1NgWait = Number(mapper1NgWaitStringWithoutErrorMessage)
    return isNaN(mapper1NgWait) ? 0 : mapper1NgWait
  } else {
    return mapper1OkWait
  }
}

const mapper2_limit2 = (abortController: AbortController) => async (sites: string[]) => {
  const site = sites[0]
  const indexOrNgMessage = getIndexOrNgMessage(abortController.abort, site, (site) =>
    site.substring(site.length - 3, site.length - 2)
  )

  if (abortController.signal.aborted) {
    checkLog(`mapper2-index = ${indexOrNgMessage} - aborted`)
    return `mapper2-index = ${indexOrNgMessage} - aborted`
  }

  const abortEvent = () => {
    checkLog(`mapper2-index = ${indexOrNgMessage} - aborted - from event`)
  }
  abortController.signal.addEventListener('abort', abortEvent)
  function removeEvent() {
    abortController.signal.removeEventListener('abort', abortEvent)
  }

  if (Number.isNaN(Number(indexOrNgMessage))) return indexOrNgMessage

  const wait = Number(indexOrNgMessage) * 1000
  try {
    const _wait = (await sleep(wait)) as number
    checkLog(`mapper2-index = ${indexOrNgMessage}`)
    const concurrency = 2
    const result = await pMap(sites, mapper1(abortController, Number(indexOrNgMessage)), {
      concurrency
    })
    const waitResults = result.map(getWaitFromMapper1Message)
    const waitResultsArray = waitResults.reduce(array_slice_2, [])
    const waitResultsArraySum = waitResultsArray.reduce(array_sum_slice_2, [])
    const sumWait = Math.max(...waitResultsArraySum)
    removeEvent()
    return `${site.substring(0, site.length - 2)} - ${_wait + sumWait}`
  } catch (error) {
    checkLog(`mapper2-index = ${indexOrNgMessage} - ng - ${error}`)
    removeEvent()
    return `${site.substring(0, site.length - 2)} - ng - ${error}`
  }
}

describe('p-map 非同期処理２つの配列', () => {
  test('使い方５ー全部同時実行からの最大２個まで同時実行', testOptions, async () => {
    const sites = [
      // ['https://test/1/1'],
      // ['https://test/2/1', 'https://test/2/2']
      //   ['https://test/3/1', 'https://test/3/2', 'https://test/3/3']
      // ['https://test/4/1', 'https://test/4/2', 'https://test/4/3', 'https://test/4/4'],
      // [
      //   'https://test/5/1',
      //   'https://test/5/2',
      //   'https://test/5/3',
      //   'https://test/5/4',
      //   'https://test/5/5'
      // ]
      [
        'https://test/7/1',
        'https://test/7/2',
        'https://test/7/3',
        'https://test/7/4',
        'https://test/7/5',
        'https://test/7/6',
        'https://test/7/7'
      ]
    ]

    const abortController = new AbortController()

    checkLog('[start]')

    try {
      const result = await pMap(sites, mapper2_limit2(abortController), {
        signal: abortController.signal
      })
      console.log(result)
    } catch (error) {
      console.log(error)
    }
  })
})
```

実行結果
```ts
time : 2025-01-26T12:10:58.904Z - text: [start]
time : 2025-01-26T12:11:05.922Z - text: mapper2-index = 7
time : 2025-01-26T12:11:06.935Z - text: https://test/7/1
time : 2025-01-26T12:11:06.935Z - text: https://test/7/3 - abort - (topIndex + lastIndex) >= 10
time : 2025-01-26T12:11:06.937Z - text: mapper2-index = 7 - aborted - from event
time : 2025-01-26T12:11:06.938Z - text: https://test/7/2 - aborted - from event
DOMException {}
time : 2025-01-26T12:11:06.941Z - text: https://test/7/4 - aborted
time : 2025-01-26T12:11:06.942Z - text: https://test/7/5 - aborted
time : 2025-01-26T12:11:06.942Z - text: https://test/7/6 - aborted
time : 2025-01-26T12:11:06.943Z - text: https://test/7/7 - aborted
```

実行結果（※中断データを除外した場合）
```ts
time : 2025-01-26T12:11:34.353Z - text: [start]
time : 2025-01-26T12:11:41.367Z - text: mapper2-index = 7
time : 2025-01-26T12:11:42.383Z - text: https://test/7/1
time : 2025-01-26T12:11:43.368Z - text: https://test/7/2 - ng - 2000 - Sleep NG after wait
[ 'https://test/7 - 9000' ]
```


## 配列が２つの場合（複雑）

### 全部同時実行からの全部同時実行

```plantuml
@startuml
robust "全体" as all

concise "https://test/1" as test1
concise "https://test/1/1" as test1_1

concise "https://test/2" as test2
concise "https://test/2/1" as test2_1
concise "https://test/2/2" as test2_2

concise "https://test/3" as test3
concise "https://test/3/1" as test3_1
concise "https://test/3/2" as test3_2
concise "https://test/3/3" as test3_3

concise "https://test/4" as test4
concise "https://test/4/1" as test4_1
concise "https://test/4/2" as test4_2
concise "https://test/4/3" as test4_3
concise "https://test/4/4" as test4_4

concise "https://test/5" as test5
concise "https://test/5/1" as test5_1
compact concise "abort" as test51c
concise "https://test/5/2" as test5_2
compact concise "abort" as test52c
concise "https://test/5/3" as test5_3
concise "https://test/5/4" as test5_4
compact concise "abort" as test54c
concise "https://test/5/5" as test5_5

concise "https://test/7" as test7
compact concise "abort" as test7c
concise "https://test/7/1" as test7_1
concise "https://test/7/2" as test7_2
concise "https://test/7/3" as test7_3
concise "https://test/7/4" as test7_4
concise "https://test/7/5" as test7_5
concise "https://test/7/6" as test7_6
concise "https://test/7/7" as test7_7

@6
test5_1 -> test51c
test5_2 -> test52c
test5_4 -> test54c
test7 -> test7c

@all
0 is 待機中 
+1 is 処理中
+5 is 中断中

@test1
1 is 処理中
+1 is {hidden}
@test1_1
2 is 処理中
+1 is {hidden}

@test2
1 is 処理中 #pink
+2 is {hidden}

@test3
1 is 処理中 #pink
+0.01 is {hidden}

@test4
1 is 処理中 #pink
+4 is {hidden}

@test5
1 is 処理中
+5 is {hidden}
@test5_1
6 is 処理中
+1 is {hidden}
@test51c
6 is aborted #gray
+0.01 is {hidden}
@test5_2
6 is 処理中 #pink
+2 is {hidden}
@test52c
6 is aborted #gray
+0.01 is {hidden}
@test5_3
6 is 処理中 #pink
+0.01 is {hidden}
@test5_4
6 is 処理中 #pink
+4 is {hidden}
@test54c
6 is aborted #gray
+0.01 is {hidden}
@test5_5
6 is abort #gray
+0.01 is {hidden}

@test7
1 is 処理中
+7 is {hidden}
@test7c
6 is aborted #gray
+0.01 is {hidden}

@enduml
```

```ts
import { describe, test } from 'vitest'
import pMap from 'p-map'

const sleep = (wait: number) =>
  new Promise((resolve, reject) => {
    const waitSeconds = wait / 1000
    if (waitSeconds % 3 === 0) {
      reject('Sleep NG before wait')
    } else if (waitSeconds % 2 === 0) {
      setTimeout(() => reject(`${wait} - Sleep NG after wait`), wait)
    } else {
      setTimeout(() => resolve(wait), wait)
    }
  })

function checkLog(text: string) {
  console.log(`time : ${new Date().toISOString()} - text: ${text}`)
}

const testOptions = {
  timeout: 1000 * 100
}

function getIndexOrNgMessage(
  abortAction: Function,
  site: string,
  getIndexAction: (site: string) => string
) {
  const index = getIndexAction(site)
  if (Number.isNaN(Number(index))) {
    checkLog(`${site} - abort - index is NaN`)
    abortAction()
    return `${site} - abort - index is NaN`
  }
  if (site[site.length - 2] !== '/') {
    checkLog(`${site} - abort - index >= 10`)
    abortAction()
    return `${site} - abort - index >= 10`
  }

  return index
}

function checkIndex(abortAction: Function, site: string, topIndex?: number, lastIndex?: number) {
  if (topIndex && lastIndex && topIndex + lastIndex >= 10) {
    checkLog(`${site} - abort - (topIndex + lastIndex) >= 10`)
    abortAction()
    return `${site} - abort - (topIndex + lastIndex) >= 10`
  }

  return 'OK'
}

const mapper1 = (abortController: AbortController, topIndex?: number) => async (site: string) => {
  const abortEvent = () => {
    checkLog(`${site} - aborted - from event`)
  }
  abortController.signal.addEventListener('abort', abortEvent)
  function removeEvent() {
    abortController.signal.removeEventListener('abort', abortEvent)
  }
  const abortAction = () => {
    removeEvent()
    abortController.abort()
  }

  if (abortController.signal.aborted) {
    checkLog(`${site} - aborted`)
    removeEvent()
    return `${site} - aborted`
  }

  const indexOrNgMessage = getIndexOrNgMessage(abortAction, site, (site) => site.slice(-1))
  if (Number.isNaN(Number(indexOrNgMessage))) {
    removeEvent()
    return indexOrNgMessage
  }

  const indexCheck = checkIndex(abortAction, site, topIndex, Number(indexOrNgMessage))
  if (indexCheck !== 'OK') {
    removeEvent()
    return indexCheck
  }

  const wait = Number(indexOrNgMessage) * 1000
  try {
    const _wait = await sleep(wait)
    checkLog(site)
    removeEvent()
    return `${site} - ok - ${_wait}`
  } catch (error) {
    checkLog(`${site} - ng - ${error}`)
    removeEvent()
    return `${site} - ng - ${error}`
  }
}
function getWaitFromMapper1Message(mapper1Message: string) {
  const mapper1OkWaitString = mapper1Message.substring(mapper1Message.indexOf('ok') + 5)
  const mapper1OkWait = Number(mapper1OkWaitString)
  if (isNaN(mapper1OkWait)) {
    const mapper1NgWaitString = mapper1Message.substring(mapper1Message.indexOf('ng') + 5)
    const mapper1NgWaitStringWithoutErrorMessage = mapper1NgWaitString.substring(
      0,
      mapper1NgWaitString.indexOf('-')
    )
    const mapper1NgWait = Number(mapper1NgWaitStringWithoutErrorMessage)
    return isNaN(mapper1NgWait) ? 0 : mapper1NgWait
  } else {
    return mapper1OkWait
  }
}

const mapper2_max = (abortController: AbortController) => async (sites: string[]) => {
  const site = sites[0]
  const indexOrNgMessage = getIndexOrNgMessage(abortController.abort, site, (site) =>
    site.substring(site.length - 3, site.length - 2)
  )

  if (abortController.signal.aborted) {
    checkLog(`mapper2-index = ${indexOrNgMessage} - aborted`)
    return `mapper2-index = ${indexOrNgMessage} - aborted`
  }

  const abortEvent = () => {
    checkLog(`mapper2-index = ${indexOrNgMessage} - aborted - from event`)
  }
  abortController.signal.addEventListener('abort', abortEvent)
  function removeEvent() {
    abortController.signal.removeEventListener('abort', abortEvent)
  }

  if (Number.isNaN(Number(indexOrNgMessage))) return indexOrNgMessage

  const wait = Number(indexOrNgMessage) * 1000
  try {
    const _wait = (await sleep(wait)) as number
    checkLog(`mapper2-index = ${indexOrNgMessage}`)
    const result = await pMap(sites, mapper1(abortController, Number(indexOrNgMessage)))
    const waitResults = result.map(getWaitFromMapper1Message)
    const maxWait = Math.max(...waitResults)
    removeEvent()
    return `${site.substring(0, site.length - 2)} - ok - ${_wait + maxWait}`
  } catch (error) {
    checkLog(`mapper2-index = ${indexOrNgMessage} - ng - ${error}`)
    removeEvent()
    return `${site.substring(0, site.length - 2)} - ng - ${error}`
  }
}

describe('p-map 非同期処理２つの配列', () => {
  test('使い方２ー全部同時実行からの全部同時実行', testOptions, async () => {
    const sites = [
      ['https://test/1/1'],
      ['https://test/2/1', 'https://test/2/2'],
      ['https://test/3/1', 'https://test/3/2', 'https://test/3/3'],
      ['https://test/4/1', 'https://test/4/2', 'https://test/4/3', 'https://test/4/4'],
      [
        'https://test/5/1',
        'https://test/5/2',
        'https://test/5/3',
        'https://test/5/4',
        'https://test/5/5'
      ],
      [
        'https://test/7/1',
        'https://test/7/2',
        'https://test/7/3',
        'https://test/7/4',
        'https://test/7/5',
        'https://test/7/6',
        'https://test/7/7'
      ]
    ]

    const abortController = new AbortController()

    checkLog('[start]')

    try {
      const result = await pMap(sites, mapper2_max(abortController), {
        signal: abortController.signal
      })
      console.log(result)
    } catch (error) {
      console.log(error)
    }
  })
})
```

実行結果
```ts
time : 2025-01-26T13:05:59.522Z - text: [start]
time : 2025-01-26T13:05:59.524Z - text: mapper2-index = 3 - ng - Sleep NG before wait
time : 2025-01-26T13:06:00.535Z - text: mapper2-index = 1
time : 2025-01-26T13:06:01.540Z - text: mapper2-index = 2 - ng - 2000 - Sleep NG after wait
time : 2025-01-26T13:06:01.541Z - text: https://test/1/1
time : 2025-01-26T13:06:03.531Z - text: mapper2-index = 4 - ng - 4000 - Sleep NG after wait
time : 2025-01-26T13:06:04.536Z - text: mapper2-index = 5
time : 2025-01-26T13:06:04.538Z - text: https://test/5/3 - ng - Sleep NG before wait
time : 2025-01-26T13:06:04.538Z - text: https://test/5/5 - abort - (topIndex + lastIndex) >= 10
time : 2025-01-26T13:06:04.543Z - text: mapper2-index = 5 - aborted - from event
time : 2025-01-26T13:06:04.543Z - text: mapper2-index = 7 - aborted - from event
time : 2025-01-26T13:06:04.544Z - text: https://test/5/1 - aborted - from event
time : 2025-01-26T13:06:04.544Z - text: https://test/5/2 - aborted - from event
time : 2025-01-26T13:06:04.544Z - text: https://test/5/4 - aborted - from event
DOMException {}
```

実行結果（※中断データを除外した場合）
```ts
time : 2025-01-26T13:07:14.725Z - text: [start]
time : 2025-01-26T13:07:14.727Z - text: mapper2-index = 3 - ng - Sleep NG before wait
time : 2025-01-26T13:07:15.732Z - text: mapper2-index = 1
time : 2025-01-26T13:07:16.734Z - text: mapper2-index = 2 - ng - 2000 - Sleep NG after wait
time : 2025-01-26T13:07:16.735Z - text: https://test/1/1
time : 2025-01-26T13:07:18.733Z - text: mapper2-index = 4 - ng - 4000 - Sleep NG after wait
time : 2025-01-26T13:07:19.737Z - text: mapper2-index = 5
time : 2025-01-26T13:07:19.738Z - text: https://test/5/3 - ng - Sleep NG before wait
time : 2025-01-26T13:07:20.745Z - text: https://test/5/1
time : 2025-01-26T13:07:21.743Z - text: mapper2-index = 7
time : 2025-01-26T13:07:21.744Z - text: https://test/5/2 - ng - 2000 - Sleep NG after wait
time : 2025-01-26T13:07:22.745Z - text: https://test/7/1
time : 2025-01-26T13:07:23.747Z - text: https://test/5/4 - ng - 4000 - Sleep NG after wait
time : 2025-01-26T13:07:23.748Z - text: https://test/7/2 - ng - 2000 - Sleep NG after wait
[
  'https://test/1 - ok - 2000',
  'https://test/2 - ng - 2000 - Sleep NG after wait',
  'https://test/3 - ng - Sleep NG before wait',
  'https://test/4 - ng - 4000 - Sleep NG after wait',
  'https://test/5 - ok - 9000',
  'https://test/7 - ok - 9000'
]
```



### 全部同時実行からの直列実行

```plantuml
@startuml
robust "全体" as all

concise "https://test/1" as test1
concise "https://test/1/1" as test1_1

concise "https://test/2" as test2
concise "https://test/2/1" as test2_1
concise "https://test/2/2" as test2_2

concise "https://test/3" as test3
concise "https://test/3/1" as test3_1
concise "https://test/3/2" as test3_2
concise "https://test/3/3" as test3_3

concise "https://test/4" as test4
concise "https://test/4/1" as test4_1
concise "https://test/4/2" as test4_2
concise "https://test/4/3" as test4_3
concise "https://test/4/4" as test4_4

concise "https://test/5" as test5
concise "https://test/5/1" as test5_1
concise "https://test/5/2" as test5_2
concise "https://test/5/3" as test5_3
concise "https://test/5/4" as test5_4
compact concise "abort" as test54c
concise "https://test/5/5" as test5_5

concise "https://test/7" as test7
concise "https://test/7/1" as test7_1
concise "https://test/7/2" as test7_2
concise "https://test/7/3" as test7_3
concise "https://test/7/4" as test7_4
compact concise "abort" as test74c
concise "https://test/7/5" as test7_5
compact concise "abort" as test75c
concise "https://test/7/6" as test7_6
compact concise "abort" as test76c
concise "https://test/7/7" as test7_7
compact concise "abort" as test77c

@11
test5_4 -> test54c
test7_4 -> test74c
test7_5 -> test75c
test7_6 -> test76c
test7_7 -> test77c

@all
0 is 待機中
+1 is 処理中
+10 is 中断中

@test1
1 is 処理中
+1 is {hidden}
@test1_1
2 is 処理中
+1 is {hidden}

@test2
1 is 処理中 #pink
+2 is {hidden}

@test3
1 is 処理中 #pink
+0.01 is {hidden}

@test4
1 is 処理中 #pink
+4 is {hidden}

@test5
1 is 処理中
+5 is {hidden}
@test5_1
6 is 処理中
+1 is {hidden}
@test5_2
7 is 処理中 #pink
+2 is {hidden}
@test5_3
9 is 処理中 #pink
+0.01 is {hidden}
@test5_4
9 is 処理中 #pink
+4 is {hidden}
@test54c
11 is aborted #gray
+0.01 is {hidden}

@test7
1 is 処理中
+7 is {hidden}
@test7_1
8 is 処理中
+1 is {hidden}
@test7_2
9 is 処理中 #pink
+2 is {hidden}
@test7_3
11 is abort #gray
+0.01 is {hidden}
@test7_4
11 is abort #gray
+0.01 is {hidden}
@test74c
11 is aborted #gray
+0.01 is {hidden}
@test7_5
11 is abort #gray
+0.01 is {hidden}
@test75c
11 is aborted #gray
+0.01 is {hidden}
@test7_6
11 is abort #gray
+0.01 is {hidden}
@test76c
11 is aborted #gray
+0.01 is {hidden}
@test7_7
11 is abort #gray
+0.01 is {hidden}
@test77c
11 is aborted #gray
+0.01 is {hidden}

@enduml
```

```ts
import { describe, test } from 'vitest'
import pMap from 'p-map'

const sleep = (wait: number) =>
  new Promise((resolve, reject) => {
    const waitSeconds = wait / 1000
    if (waitSeconds % 3 === 0) {
      reject('Sleep NG before wait')
    } else if (waitSeconds % 2 === 0) {
      setTimeout(() => reject(`${wait} - Sleep NG after wait`), wait)
    } else {
      setTimeout(() => resolve(wait), wait)
    }
  })

function checkLog(text: string) {
  console.log(`time : ${new Date().toISOString()} - text: ${text}`)
}

const testOptions = {
  timeout: 1000 * 100
}

function sum(a: number, b: number) {
  return a + b
}

function getIndexOrNgMessage(
  abortAction: Function,
  site: string,
  getIndexAction: (site: string) => string
) {
  const index = getIndexAction(site)
  if (Number.isNaN(Number(index))) {
    checkLog(`${site} - abort - index is NaN`)
    abortAction()
    return `${site} - abort - index is NaN`
  }
  if (site[site.length - 2] !== '/') {
    checkLog(`${site} - abort - index >= 10`)
    abortAction()
    return `${site} - abort - index >= 10`
  }

  return index
}

function checkIndex(abortAction: Function, site: string, topIndex?: number, lastIndex?: number) {
  if (topIndex && lastIndex && topIndex + lastIndex >= 10) {
    checkLog(`${site} - abort - (topIndex + lastIndex) >= 10`)
    abortAction()
    return `${site} - abort - (topIndex + lastIndex) >= 10`
  }

  return 'OK'
}

const mapper1 = (abortController: AbortController, topIndex?: number) => async (site: string) => {
  const abortEvent = () => {
    checkLog(`${site} - aborted - from event`)
  }
  abortController.signal.addEventListener('abort', abortEvent)
  function removeEvent() {
    abortController.signal.removeEventListener('abort', abortEvent)
  }
  const abortAction = () => {
    removeEvent()
    abortController.abort()
  }

  if (abortController.signal.aborted) {
    checkLog(`${site} - aborted`)
    removeEvent()
    return `${site} - aborted`
  }

  const indexOrNgMessage = getIndexOrNgMessage(abortAction, site, (site) => site.slice(-1))
  if (Number.isNaN(Number(indexOrNgMessage))) {
    removeEvent()
    return indexOrNgMessage
  }

  const indexCheck = checkIndex(abortAction, site, topIndex, Number(indexOrNgMessage))
  if (indexCheck !== 'OK') {
    removeEvent()
    return indexCheck
  }

  const wait = Number(indexOrNgMessage) * 1000
  try {
    const _wait = await sleep(wait)
    checkLog(site)
    removeEvent()
    return `${site} - ok - ${_wait}`
  } catch (error) {
    checkLog(`${site} - ng - ${error}`)
    removeEvent()
    return `${site} - ng - ${error}`
  }
}
function getWaitFromMapper1Message(mapper1Message: string) {
  const mapper1OkWaitString = mapper1Message.substring(mapper1Message.indexOf('ok') + 5)
  const mapper1OkWait = Number(mapper1OkWaitString)
  if (isNaN(mapper1OkWait)) {
    const mapper1NgWaitString = mapper1Message.substring(mapper1Message.indexOf('ng') + 5)
    const mapper1NgWaitStringWithoutErrorMessage = mapper1NgWaitString.substring(
      0,
      mapper1NgWaitString.indexOf('-')
    )
    const mapper1NgWait = Number(mapper1NgWaitStringWithoutErrorMessage)
    return isNaN(mapper1NgWait) ? 0 : mapper1NgWait
  } else {
    return mapper1OkWait
  }
}

const mapper2_sum = (abortController: AbortController) => async (sites: string[]) => {
  const site = sites[0]
  const indexOrNgMessage = getIndexOrNgMessage(abortController.abort, site, (site) =>
    site.substring(site.length - 3, site.length - 2)
  )

  if (abortController.signal.aborted) {
    checkLog(`mapper2-index = ${indexOrNgMessage} - aborted`)
    return `mapper2-index = ${indexOrNgMessage} - aborted`
  }

  const abortEvent = () => {
    checkLog(`mapper2-index = ${indexOrNgMessage} - aborted - from event`)
  }
  abortController.signal.addEventListener('abort', abortEvent)
  function removeEvent() {
    abortController.signal.removeEventListener('abort', abortEvent)
  }

  if (Number.isNaN(Number(indexOrNgMessage))) return indexOrNgMessage

  const wait = Number(indexOrNgMessage) * 1000
  try {
    const _wait = (await sleep(wait)) as number
    checkLog(`mapper2-index = ${indexOrNgMessage}`)
    const concurrency = 1
    const result = await pMap(sites, mapper1(abortController, Number(indexOrNgMessage)), {
      concurrency
    })
    const waitResults = result.map(getWaitFromMapper1Message)
    const sumWait = waitResults.reduce(sum, 0)
    removeEvent()
    return `${site.substring(0, site.length - 2)} - ${_wait + sumWait}`
  } catch (error) {
    checkLog(`mapper2-index = ${indexOrNgMessage} - ng - ${error}`)
    removeEvent()
    return `${site.substring(0, site.length - 2)} - ng - ${error}`
  }
}

describe('p-map 非同期処理２つの配列', () => {
  test('使い方４ー全部同時実行からの直列実行', testOptions, async () => {
    const sites = [
      ['https://test/1/1'],
      ['https://test/2/1', 'https://test/2/2'],
      ['https://test/3/1', 'https://test/3/2', 'https://test/3/3'],
      ['https://test/4/1', 'https://test/4/2', 'https://test/4/3', 'https://test/4/4'],
      [
        'https://test/5/1',
        'https://test/5/2',
        'https://test/5/3',
        'https://test/5/4',
        'https://test/5/5'
      ],
      [
        'https://test/7/1',
        'https://test/7/2',
        'https://test/7/3',
        'https://test/7/4',
        'https://test/7/5',
        'https://test/7/6',
        'https://test/7/7'
      ]
    ]

    const abortController = new AbortController()

    checkLog('[start]')

    try {
      const result = await pMap(sites, mapper2_sum(abortController), {
        signal: abortController.signal
      })
      console.log(result)
    } catch (error) {
      console.log(error)
    }
  })
})
```

実行結果
```ts
time : 2025-01-26T13:35:28.440Z - text: [start]
time : 2025-01-26T13:35:28.442Z - text: mapper2-index = 3 - ng - Sleep NG before wait
time : 2025-01-26T13:35:29.447Z - text: mapper2-index = 1
time : 2025-01-26T13:35:30.452Z - text: mapper2-index = 2 - ng - 2000 - Sleep NG after wait
time : 2025-01-26T13:35:30.452Z - text: https://test/1/1
time : 2025-01-26T13:35:32.449Z - text: mapper2-index = 4 - ng - 4000 - Sleep NG after wait
time : 2025-01-26T13:35:33.450Z - text: mapper2-index = 5
time : 2025-01-26T13:35:34.453Z - text: https://test/5/1
time : 2025-01-26T13:35:35.452Z - text: mapper2-index = 7
time : 2025-01-26T13:35:36.467Z - text: https://test/5/2 - ng - 2000 - Sleep NG after wait
time : 2025-01-26T13:35:36.468Z - text: https://test/5/3 - ng - Sleep NG before wait
time : 2025-01-26T13:35:36.469Z - text: https://test/7/1
time : 2025-01-26T13:35:38.485Z - text: https://test/7/2 - ng - 2000 - Sleep NG after wait
time : 2025-01-26T13:35:38.486Z - text: https://test/7/3 - abort - (topIndex + lastIndex) >= 10
time : 2025-01-26T13:35:38.490Z - text: mapper2-index = 5 - aborted - from event
time : 2025-01-26T13:35:38.491Z - text: mapper2-index = 7 - aborted - from event
time : 2025-01-26T13:35:38.491Z - text: https://test/5/4 - aborted - from event
DOMException {}
time : 2025-01-26T13:35:38.496Z - text: https://test/7/4 - aborted
time : 2025-01-26T13:35:38.498Z - text: https://test/7/5 - aborted
time : 2025-01-26T13:35:38.499Z - text: https://test/7/6 - aborted
time : 2025-01-26T13:35:38.500Z - text: https://test/7/7 - aborted
```

実行結果（※中断データを除外した場合）
```ts
time : 2025-01-26T13:36:05.380Z - text: [start]
time : 2025-01-26T13:36:05.382Z - text: mapper2-index = 3 - ng - Sleep NG before wait
time : 2025-01-26T13:36:06.395Z - text: mapper2-index = 1
time : 2025-01-26T13:36:07.394Z - text: mapper2-index = 2 - ng - 2000 - Sleep NG after wait
time : 2025-01-26T13:36:07.396Z - text: https://test/1/1
time : 2025-01-26T13:36:09.397Z - text: mapper2-index = 4 - ng - 4000 - Sleep NG after wait
time : 2025-01-26T13:36:10.388Z - text: mapper2-index = 5
time : 2025-01-26T13:36:11.392Z - text: https://test/5/1
time : 2025-01-26T13:36:12.396Z - text: mapper2-index = 7
time : 2025-01-26T13:36:13.399Z - text: https://test/5/2 - ng - 2000 - Sleep NG after wait
time : 2025-01-26T13:36:13.400Z - text: https://test/5/3 - ng - Sleep NG before wait
time : 2025-01-26T13:36:13.402Z - text: https://test/7/1
time : 2025-01-26T13:36:15.411Z - text: https://test/7/2 - ng - 2000 - Sleep NG after wait
time : 2025-01-26T13:36:17.401Z - text: https://test/5/4 - ng - 4000 - Sleep NG after wait
[
  'https://test/1 - 2000',
  'https://test/2 - ng - 2000 - Sleep NG after wait',
  'https://test/3 - ng - Sleep NG before wait',
  'https://test/4 - ng - 4000 - Sleep NG after wait',
  'https://test/5 - 12000',
  'https://test/7 - 10000'
]
```




### 全部同時実行からの最大２個まで同時実行

```plantuml
@startuml
robust "全体" as all

concise "https://test/1" as test1
concise "https://test/1/1" as test1_1

concise "https://test/2" as test2
concise "https://test/2/1" as test2_1
concise "https://test/2/2" as test2_2

concise "https://test/3" as test3
concise "https://test/3/1" as test3_1
concise "https://test/3/2" as test3_2
concise "https://test/3/3" as test3_3

concise "https://test/4" as test4
concise "https://test/4/1" as test4_1
concise "https://test/4/2" as test4_2
concise "https://test/4/3" as test4_3
concise "https://test/4/4" as test4_4

concise "https://test/5" as test5
concise "https://test/5/1" as test5_1
concise "https://test/5/2" as test5_2
concise "https://test/5/3" as test5_3
concise "https://test/5/4" as test5_4
compact concise "abort" as test54c
concise "https://test/5/5" as test5_5

concise "https://test/7" as test7
concise "https://test/7/1" as test7_1
compact concise "abort" as test71c
concise "https://test/7/2" as test7_2
compact concise "abort" as test72c
concise "https://test/7/3" as test7_3
concise "https://test/7/4" as test7_4
concise "https://test/7/5" as test7_5
concise "https://test/7/6" as test7_6
concise "https://test/7/7" as test7_7

@8
test5_4 -> test54c
test7_1 -> test71c
test7_2 -> test72c

@all
0 is 待機中
+1 is 処理中
+7 is 中断中

@test1
1 is 処理中
+1 is {hidden}
@test1_1
2 is 処理中
+1 is {hidden}

@test2
1 is 処理中 #pink
+2 is {hidden}

@test3
1 is 処理中 #pink
+0.01 is {hidden}

@test4
1 is 処理中 #pink
+4 is {hidden}

@test5
1 is 処理中
+5 is {hidden}
@test5_1
6 is 処理中
+1 is {hidden}
@test5_2
6 is 処理中 #pink
+2 is {hidden}
@test5_3
7 is 処理中 #pink
+0.01 is {hidden}
@test5_4
7 is 処理中 #pink
+4 is {hidden}
@test54c
8 is aborted #gray
+0.01 is {hidden}
@test5_5
8 is abort #gray
+0.01 is {hidden}

@test7
1 is 処理中
+7 is {hidden}
@test7_1
8 is 処理中
+1 is {hidden}
@test71c
8 is aborted #gray
+0.01 is {hidden}
@test7_2
8 is 処理中 #pink
+2 is {hidden}
@test72c
8 is aborted #gray
+0.01 is {hidden}

@enduml
```

```ts
import { describe, test } from 'vitest'
import pMap from 'p-map'

const sleep = (wait: number) =>
  new Promise((resolve, reject) => {
    const waitSeconds = wait / 1000
    if (waitSeconds % 3 === 0) {
      reject('Sleep NG before wait')
    } else if (waitSeconds % 2 === 0) {
      setTimeout(() => reject(`${wait} - Sleep NG after wait`), wait)
    } else {
      setTimeout(() => resolve(wait), wait)
    }
  })

function checkLog(text: string) {
  console.log(`time : ${new Date().toISOString()} - text: ${text}`)
}

const testOptions = {
  timeout: 1000 * 100
}

function array_slice_2(acc: number[][], cur: number) {
  const topLengt = acc.length
  if (topLengt == 0) {
    acc.push([cur])
  } else if (acc[topLengt - 1].length == 2) {
    acc.push([cur])
  } else {
    acc[topLengt - 1].push(cur)
  }

  return acc
}

function array_sum_slice_2(acc: number[], cur: number[]) {
  const accLengt = acc.length
  if (accLengt == 0) {
    acc.push(...cur)
  } else {
    const curLength = cur.length
    const min = Math.min(acc[0], acc[1])
    const minIndex = acc[0] == min ? 0 : 1
    acc[minIndex] += cur[0]

    if (curLength == 2) {
      const min2 = Math.min(acc[0], acc[1])
      const min2Index = acc[0] == min2 ? 0 : 1
      acc[min2Index] += cur[1]
    }
  }

  return acc
}

function getIndexOrNgMessage(
  abortAction: Function,
  site: string,
  getIndexAction: (site: string) => string
) {
  const index = getIndexAction(site)
  if (Number.isNaN(Number(index))) {
    checkLog(`${site} - abort - index is NaN`)
    abortAction()
    return `${site} - abort - index is NaN`
  }
  if (site[site.length - 2] !== '/') {
    checkLog(`${site} - abort - index >= 10`)
    abortAction()
    return `${site} - abort - index >= 10`
  }

  return index
}

function checkIndex(abortAction: Function, site: string, topIndex?: number, lastIndex?: number) {
  if (topIndex && lastIndex && topIndex + lastIndex >= 10) {
    checkLog(`${site} - abort - (topIndex + lastIndex) >= 10`)
    abortAction()
    return `${site} - abort - (topIndex + lastIndex) >= 10`
  }

  return 'OK'
}

const mapper1 = (abortController: AbortController, topIndex?: number) => async (site: string) => {
  const abortEvent = () => {
    checkLog(`${site} - aborted - from event`)
  }
  abortController.signal.addEventListener('abort', abortEvent)
  function removeEvent() {
    abortController.signal.removeEventListener('abort', abortEvent)
  }
  const abortAction = () => {
    removeEvent()
    abortController.abort()
  }

  if (abortController.signal.aborted) {
    checkLog(`${site} - aborted`)
    removeEvent()
    return `${site} - aborted`
  }

  const indexOrNgMessage = getIndexOrNgMessage(abortAction, site, (site) => site.slice(-1))
  if (Number.isNaN(Number(indexOrNgMessage))) {
    removeEvent()
    return indexOrNgMessage
  }

  const indexCheck = checkIndex(abortAction, site, topIndex, Number(indexOrNgMessage))
  if (indexCheck !== 'OK') {
    removeEvent()
    return indexCheck
  }

  const wait = Number(indexOrNgMessage) * 1000
  try {
    const _wait = await sleep(wait)
    checkLog(site)
    removeEvent()
    return `${site} - ok - ${_wait}`
  } catch (error) {
    checkLog(`${site} - ng - ${error}`)
    removeEvent()
    return `${site} - ng - ${error}`
  }
}
function getWaitFromMapper1Message(mapper1Message: string) {
  const mapper1OkWaitString = mapper1Message.substring(mapper1Message.indexOf('ok') + 5)
  const mapper1OkWait = Number(mapper1OkWaitString)
  if (isNaN(mapper1OkWait)) {
    const mapper1NgWaitString = mapper1Message.substring(mapper1Message.indexOf('ng') + 5)
    const mapper1NgWaitStringWithoutErrorMessage = mapper1NgWaitString.substring(
      0,
      mapper1NgWaitString.indexOf('-')
    )
    const mapper1NgWait = Number(mapper1NgWaitStringWithoutErrorMessage)
    return isNaN(mapper1NgWait) ? 0 : mapper1NgWait
  } else {
    return mapper1OkWait
  }
}

const mapper2_limit2 = (abortController: AbortController) => async (sites: string[]) => {
  const site = sites[0]
  const indexOrNgMessage = getIndexOrNgMessage(abortController.abort, site, (site) =>
    site.substring(site.length - 3, site.length - 2)
  )

  if (abortController.signal.aborted) {
    checkLog(`mapper2-index = ${indexOrNgMessage} - aborted`)
    return `mapper2-index = ${indexOrNgMessage} - aborted`
  }

  const abortEvent = () => {
    checkLog(`mapper2-index = ${indexOrNgMessage} - aborted - from event`)
  }
  abortController.signal.addEventListener('abort', abortEvent)
  function removeEvent() {
    abortController.signal.removeEventListener('abort', abortEvent)
  }

  if (Number.isNaN(Number(indexOrNgMessage))) return indexOrNgMessage

  const wait = Number(indexOrNgMessage) * 1000
  try {
    const _wait = (await sleep(wait)) as number
    checkLog(`mapper2-index = ${indexOrNgMessage}`)
    const concurrency = 2
    const result = await pMap(sites, mapper1(abortController, Number(indexOrNgMessage)), {
      concurrency
    })
    const waitResults = result.map(getWaitFromMapper1Message)
    const waitResultsArray = waitResults.reduce(array_slice_2, [])
    const waitResultsArraySum = waitResultsArray.reduce(array_sum_slice_2, [])
    const sumWait = Math.max(...waitResultsArraySum)
    removeEvent()
    return `${site.substring(0, site.length - 2)} - ${_wait + sumWait}`
  } catch (error) {
    checkLog(`mapper2-index = ${indexOrNgMessage} - ng - ${error}`)
    removeEvent()
    return `${site.substring(0, site.length - 2)} - ng - ${error}`
  }
}

describe('p-map 非同期処理２つの配列', () => {
  test('使い方６ー全部同時実行からの最大２個まで同時実行', testOptions, async () => {
    const sites = [
      ['https://test/1/1'],
      ['https://test/2/1', 'https://test/2/2'],
      ['https://test/3/1', 'https://test/3/2', 'https://test/3/3'],
      ['https://test/4/1', 'https://test/4/2', 'https://test/4/3', 'https://test/4/4'],
      [
        'https://test/5/1',
        'https://test/5/2',
        'https://test/5/3',
        'https://test/5/4',
        'https://test/5/5'
      ],
      [
        'https://test/7/1',
        'https://test/7/2',
        'https://test/7/3',
        'https://test/7/4',
        'https://test/7/5',
        'https://test/7/6',
        'https://test/7/7'
      ]
    ]

    checkLog('[start]')

    const abortController = new AbortController()

    try {
      const result = await pMap(sites, mapper2_limit2(abortController), {
        signal: abortController.signal
      })
      console.log(result)
    } catch (error) {
      console.log(error)
    }
  })
})
```

実行結果
```ts
time : 2025-01-26T14:00:45.498Z - text: [start]
time : 2025-01-26T14:00:45.501Z - text: mapper2-index = 3 - ng - Sleep NG before wait
time : 2025-01-26T14:00:46.503Z - text: mapper2-index = 1
time : 2025-01-26T14:00:47.507Z - text: mapper2-index = 2 - ng - 2000 - Sleep NG after wait
time : 2025-01-26T14:00:47.508Z - text: https://test/1/1
time : 2025-01-26T14:00:49.503Z - text: mapper2-index = 4 - ng - 4000 - Sleep NG after wait
time : 2025-01-26T14:00:50.508Z - text: mapper2-index = 5
time : 2025-01-26T14:00:51.524Z - text: https://test/5/1
time : 2025-01-26T14:00:51.525Z - text: https://test/5/3 - ng - Sleep NG before wait
time : 2025-01-26T14:00:52.508Z - text: mapper2-index = 7
time : 2025-01-26T14:00:52.510Z - text: https://test/5/2 - ng - 2000 - Sleep NG after wait
time : 2025-01-26T14:00:52.510Z - text: https://test/5/5 - abort - (topIndex + lastIndex) >= 10
time : 2025-01-26T14:00:52.513Z - text: mapper2-index = 5 - aborted - from event
time : 2025-01-26T14:00:52.513Z - text: mapper2-index = 7 - aborted - from event
time : 2025-01-26T14:00:52.513Z - text: https://test/5/4 - aborted - from event
time : 2025-01-26T14:00:52.514Z - text: https://test/7/1 - aborted - from event
time : 2025-01-26T14:00:52.514Z - text: https://test/7/2 - aborted - from event
DOMException {}
```

実行結果（※中断データを除外した場合）
```ts
time : 2025-01-26T14:01:11.965Z - text: [start]
time : 2025-01-26T14:01:11.968Z - text: mapper2-index = 3 - ng - Sleep NG before wait
time : 2025-01-26T14:01:12.970Z - text: mapper2-index = 1
time : 2025-01-26T14:01:13.977Z - text: mapper2-index = 2 - ng - 2000 - Sleep NG after wait
time : 2025-01-26T14:01:13.978Z - text: https://test/1/1
time : 2025-01-26T14:01:15.982Z - text: mapper2-index = 4 - ng - 4000 - Sleep NG after wait
time : 2025-01-26T14:01:16.983Z - text: mapper2-index = 5
time : 2025-01-26T14:01:17.993Z - text: https://test/5/1
time : 2025-01-26T14:01:17.994Z - text: https://test/5/3 - ng - Sleep NG before wait
time : 2025-01-26T14:01:18.973Z - text: mapper2-index = 7
time : 2025-01-26T14:01:18.987Z - text: https://test/5/2 - ng - 2000 - Sleep NG after wait
time : 2025-01-26T14:01:19.978Z - text: https://test/7/1
time : 2025-01-26T14:01:20.983Z - text: https://test/7/2 - ng - 2000 - Sleep NG after wait
time : 2025-01-26T14:01:21.997Z - text: https://test/5/4 - ng - 4000 - Sleep NG after wait
[
  'https://test/1 - 2000',
  'https://test/2 - ng - 2000 - Sleep NG after wait',
  'https://test/3 - ng - Sleep NG before wait',
  'https://test/4 - ng - 4000 - Sleep NG after wait',
  'https://test/5 - 10000',
  'https://test/7 - 9000'
]
```