fp-tsで配列の配列の非同期処理のPromiseのreject対応

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

# 前回

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

## 配列が１つの場合

### 全部同時実行

```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

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

@test1
1 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}

@enduml
```

```ts
import { describe, test } from 'vitest'
import * as TE from 'fp-ts/TaskEither'
import { pipe } from 'fp-ts/lib/function'
import * as RA from 'fp-ts/ReadonlyArray'
import * as E from 'fp-ts/Either'

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)
    }
  }) as Promise<number>

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

const testOptions = {
  timeout: 1000 * 100
}

function getLastIndex(site: string) {
  const index = site.slice(-1)
  return Number(index)
}

function getWaitNumber(index: number) {
  const wait = index * 1000
  return wait
}

function checkOkNg_FromMessage(aboutOkMessage: string) {
  const resultMessage: E.Either<string, string> =
    aboutOkMessage.indexOf('ng') > 0 ? E.left(aboutOkMessage) : E.right(aboutOkMessage)
  return resultMessage
}

const do_sleep1 = (site: string) => (wait: number) =>
  TE.tryCatch(
    () =>
      sleep(wait)
        .then((_wait) => {
          checkLog(site)
          return `${site} - ok - ${_wait}`
        })
        .catch((error) => {
          checkLog(`${site} - ng - ${error}`)
          return `${site} - ng - ${error}`
        }),
    () => 'Error 1'
  )

const mapper1 = (site: string) => {
  const action = pipe(site, getLastIndex, getWaitNumber, do_sleep1(site))
  return action
}

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

    checkLog('[start]')

    const aboutOkAction = pipe(sites, RA.traverse(TE.ApplicativePar)(mapper1))
    const aboutOkResult = await aboutOkAction()
    const result = pipe(
      aboutOkResult,
      E.foldW(
        (error) => E.left(error),
        (aboutOkMessageArray: readonly string[]) =>
          pipe(aboutOkMessageArray, RA.partitionMap(checkOkNg_FromMessage))
      )
    )

    console.log(result)
  })
})
```

実行結果
```ts
time : 2025-01-19T08:03:58.381Z - text: [start]
time : 2025-01-19T08:03:58.384Z - text: https://test/3 - ng - Sleep NG before wait
time : 2025-01-19T08:03:59.390Z - text: https://test/1
time : 2025-01-19T08:04:00.395Z - text: https://test/2 - ng - 2000 - Sleep NG after wait
time : 2025-01-19T08:04:02.393Z - text: https://test/4 - ng - 4000 - Sleep NG after wait
time : 2025-01-19T08:04:03.392Z - text: https://test/5
{
  left: [
    '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'
  ],
  right: [ 'https://test/1 - ok - 1000', '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

@all
0 is 待機中
+1 is 処理中
+11 is 待機中

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

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

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

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

@test5
7 is 処理中
+5 is {hidden}

@enduml
```

```ts
import { describe, test } from 'vitest'
import * as TE from 'fp-ts/TaskEither'
import { pipe } from 'fp-ts/lib/function'
import * as RA from 'fp-ts/ReadonlyArray'
import * as E from 'fp-ts/Either'

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)
    }
  }) as Promise<number>

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

const testOptions = {
  timeout: 1000 * 100
}

function getLastIndex(site: string) {
  const index = site.slice(-1)
  return Number(index)
}

function getWaitNumber(index: number) {
  const wait = index * 1000
  return wait
}

function checkOkNg_FromMessage(aboutOkMessage: string) {
  const resultMessage: E.Either<string, string> =
    aboutOkMessage.indexOf('ng') > 0 ? E.left(aboutOkMessage) : E.right(aboutOkMessage)
  return resultMessage
}

const do_sleep1 = (site: string) => (wait: number) =>
  TE.tryCatch(
    () =>
      sleep(wait)
        .then((_wait) => {
          checkLog(site)
          return `${site} - ok - ${_wait}`
        })
        .catch((error) => {
          checkLog(`${site} - ng - ${error}`)
          return `${site} - ng - ${error}`
        }),
    () => 'Error 1'
  )

const mapper1 = (site: string) => {
  const action = pipe(site, getLastIndex, getWaitNumber, do_sleep1(site))
  return action
}

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

    checkLog('[start]')

    const limit = 2
    const chunkSites = pipe(sites, RA.chunksOf(limit))
    const aboutOkAction = pipe(chunkSites, RA.map(RA.traverse(TE.ApplicativePar)(mapper1)))
    const aboutOkFlatAction = pipe(
      aboutOkAction,
      RA.sequence(TE.ApplicativeSeq),
      TE.map(RA.flatten)
    )
    const aboutOkResult = await aboutOkFlatAction()
    const result = pipe(
      aboutOkResult,
      E.foldW(
        (error) => E.left(error),
        (aboutOkMessageArray: readonly string[]) =>
          pipe(aboutOkMessageArray, RA.partitionMap(checkOkNg_FromMessage))
      )
    )

    console.log(result)
  })
})
```

実行結果
```ts
time : 2025-01-19T08:05:17.141Z - text: [start]
time : 2025-01-19T08:05:18.154Z - text: https://test/1
time : 2025-01-19T08:05:19.148Z - text: https://test/2 - ng - 2000 - Sleep NG after wait
time : 2025-01-19T08:05:19.149Z - text: https://test/3 - ng - Sleep NG before wait
time : 2025-01-19T08:05:23.151Z - text: https://test/4 - ng - 4000 - Sleep NG after wait
time : 2025-01-19T08:05:28.160Z - text: https://test/5
{
  left: [
    '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'
  ],
  right: [ 'https://test/1 - ok - 1000', 'https://test/5 - ok - 5000' ]
}
```


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

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

```plantuml
@startuml
robust "全体" as all
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
concise "https://test/5/5" as test5_5

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

@test5
1 is 処理中
+5 is {hidden}

@test5_1
6 is 処理中
+1 is {hidden}

@test5_2
6 is 処理中 #pink
+2 is {hidden}

@test5_3
6 is 処理中 #pink
+0.01 is {hidden}

@test5_4
6 is 処理中 #pink
+4 is {hidden}

@test5_5
6 is 処理中
+5 is {hidden}

@enduml
```

```ts
import { describe, test } from 'vitest'
import * as TE from 'fp-ts/TaskEither'
import { pipe } from 'fp-ts/lib/function'
import * as RA from 'fp-ts/ReadonlyArray'
import * as E from 'fp-ts/Either'

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)
    }
  }) as Promise<number>

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

const testOptions = {
  timeout: 1000 * 100
}

function getLastIndex(site: string) {
  const index = site.slice(-1)
  return Number(index)
}
function getTopIndex(site: string) {
  const index = site.substring(site.length - 3, site.length - 2)
  return Number(index)
}

function getWaitNumber(index: number) {
  const wait = index * 1000
  return wait
}

function checkOkNg_FromMessage(aboutOkMessage: string) {
  const resultMessage: E.Either<string, string> =
    aboutOkMessage.indexOf('ng') > 0 ? E.left(aboutOkMessage) : E.right(aboutOkMessage)
  return resultMessage
}

const do_sleep1 = (site: string) => (wait: number) =>
  TE.tryCatch(
    () =>
      sleep(wait)
        .then((_wait) => {
          checkLog(site)
          return `${site} - ok - ${_wait}`
        })
        .catch((error) => {
          checkLog(`${site} - ng - ${error}`)
          return `${site} - ng - ${error}`
        }),
    () => 'Error 1'
  )
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 do_sleep2 = (site: string) => (wait: number) =>
  TE.tryCatch(
    () =>
      sleep(wait)
        .then((_wait) => {
          const index = getTopIndex(site)
          checkLog(`mapper2-index = ${index}`)
          return (opWait: number) => `${site.substring(0, site.length - 2)} - ${_wait + opWait}`
        })
        .catch((error) => {
          const index = getTopIndex(site)
          checkLog(`mapper2-index = ${index} - ng - ${error}`)
          return () => `${site.substring(0, site.length - 2)} - ng - ${error}`
        }),
    () => 'Error 2'
  )

const op_message1_and_2 =
  (op: (a: number, b: number) => number) =>
  ([message2, message1Array]: [
    ((opWait: number) => string) | (() => string),
    readonly string[]
  ]) => {
    const opWait = pipe(message1Array, RA.map(getWaitFromMapper1Message), RA.reduce(0, op))
    return TE.right(message2(opWait))
  }

const mapper1 = (site: string) => {
  const action = pipe(site, getLastIndex, getWaitNumber, do_sleep1(site))
  return action
}

function action2_and_1(
  action2: TE.TaskEither<string, ((opWait: number) => string) | (() => string)>,
  action1: TE.TaskEither<string, readonly string[]>,
  op: (a: number, b: number) => number
) {
  return pipe(
    action2,
    TE.fold(
      (error2) => TE.right(error2),
      (message2) =>
        pipe(
          message2(123),
          checkOkNg_FromMessage,
          E.fold(
            (ngMessage2) => TE.right(ngMessage2),
            () =>
              pipe(
                action1,
                TE.foldW(
                  (error1) => TE.right(error1),
                  (message1) => op_message1_and_2(op)([message2, message1])
                )
              )
          )
        )
    )
  )
}

const mapper2_par = (sites: string[]) => {
  const site = sites[0]
  const action2 = pipe(site, getTopIndex, getWaitNumber, do_sleep2(site))
  const action1 = pipe(sites, RA.traverse(TE.ApplicativePar)(mapper1))
  const action = action2_and_1(action2, action1, Math.max)
  return action
}

describe('fp-ts 非同期処理２つの配列', () => {
  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'
      ]
    ]

    checkLog('[start]')

    const aboutOkAction = pipe(sites, RA.traverse(TE.ApplicativePar)(mapper2_par))
    const aboutOkResult = await aboutOkAction()
    const result = pipe(
      aboutOkResult,
      E.foldW(
        (error) => E.left(error),
        (aboutOkMessageArray: readonly string[]) =>
          pipe(aboutOkMessageArray, RA.partitionMap(checkOkNg_FromMessage))
      )
    )

    console.log(result)
  })
})
```

実行結果
```ts
time : 2025-01-19T08:09:18.172Z - text: [start]
time : 2025-01-19T08:09:23.185Z - text: mapper2-index = 5
time : 2025-01-19T08:09:23.187Z - text: https://test/5/3 - ng - Sleep NG before wait
time : 2025-01-19T08:09:24.199Z - text: https://test/5/1
time : 2025-01-19T08:09:25.201Z - text: https://test/5/2 - ng - 2000 - Sleep NG after wait
time : 2025-01-19T08:09:27.192Z - text: https://test/5/4 - ng - 4000 - Sleep NG after wait
time : 2025-01-19T08:09:28.194Z - text: https://test/5/5
{ left: [], right: [ 'https://test/5 - 10000' ] }
```


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

```plantuml
@startuml
robust "全体" as all
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
concise "https://test/5/5" as test5_5

@all
0 is 待機中
+1 is 処理中
+17 is 待機中

@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}

@test5_5
13 is 処理中
+5 is {hidden}

@enduml
```

```ts
import { describe, test } from 'vitest'
import * as TE from 'fp-ts/TaskEither'
import { pipe } from 'fp-ts/lib/function'
import * as RA from 'fp-ts/ReadonlyArray'
import * as E from 'fp-ts/Either'

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)
    }
  }) as Promise<number>

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 getLastIndex(site: string) {
  const index = site.slice(-1)
  return Number(index)
}
function getTopIndex(site: string) {
  const index = site.substring(site.length - 3, site.length - 2)
  return Number(index)
}

function getWaitNumber(index: number) {
  const wait = index * 1000
  return wait
}

function checkOkNg_FromMessage(aboutOkMessage: string) {
  const resultMessage: E.Either<string, string> =
    aboutOkMessage.indexOf('ng') > 0 ? E.left(aboutOkMessage) : E.right(aboutOkMessage)
  return resultMessage
}

const do_sleep1 = (site: string) => (wait: number) =>
  TE.tryCatch(
    () =>
      sleep(wait)
        .then((_wait) => {
          checkLog(site)
          return `${site} - ok - ${_wait}`
        })
        .catch((error) => {
          checkLog(`${site} - ng - ${error}`)
          return `${site} - ng - ${error}`
        }),
    () => 'Error 1'
  )
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 do_sleep2 = (site: string) => (wait: number) =>
  TE.tryCatch(
    () =>
      sleep(wait)
        .then((_wait) => {
          const index = getTopIndex(site)
          checkLog(`mapper2-index = ${index}`)
          return (opWait: number) => `${site.substring(0, site.length - 2)} - ${_wait + opWait}`
        })
        .catch((error) => {
          const index = getTopIndex(site)
          checkLog(`mapper2-index = ${index} - ng - ${error}`)
          return () => `${site.substring(0, site.length - 2)} - ng - ${error}`
        }),
    () => 'Error 2'
  )

const op_message1_and_2 =
  (op: (a: number, b: number) => number) =>
  ([message2, message1Array]: [
    ((opWait: number) => string) | (() => string),
    readonly string[]
  ]) => {
    const opWait = pipe(message1Array, RA.map(getWaitFromMapper1Message), RA.reduce(0, op))
    return TE.right(message2(opWait))
  }

const mapper1 = (site: string) => {
  const action = pipe(site, getLastIndex, getWaitNumber, do_sleep1(site))
  return action
}

function action2_and_1(
  action2: TE.TaskEither<string, ((opWait: number) => string) | (() => string)>,
  action1: TE.TaskEither<string, readonly string[]>,
  op: (a: number, b: number) => number
) {
  return pipe(
    action2,
    TE.fold(
      (error2) => TE.right(error2),
      (message2) =>
        pipe(
          message2(123),
          checkOkNg_FromMessage,
          E.fold(
            (ngMessage2) => TE.right(ngMessage2),
            () =>
              pipe(
                action1,
                TE.foldW(
                  (error1) => TE.right(error1),
                  (message1) => op_message1_and_2(op)([message2, message1])
                )
              )
          )
        )
    )
  )
}

const mapper2_seq = (sites: string[]) => {
  const site = sites[0]
  const action2 = pipe(site, getTopIndex, getWaitNumber, do_sleep2(site))
  const action1 = pipe(sites, RA.traverse(TE.ApplicativeSeq)(mapper1))
  const action = action2_and_1(action2, action1, sum)
  return action
}

describe('fp-ts 非同期処理２つの配列', () => {
  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'
      ]
    ]

    checkLog('[start]')

    const aboutOkAction = pipe(sites, RA.traverse(TE.ApplicativePar)(mapper2_seq))
    const aboutOkResult = await aboutOkAction()
    const result = pipe(
      aboutOkResult,
      E.foldW(
        (error) => E.left(error),
        (aboutOkMessageArray: readonly string[]) =>
          pipe(aboutOkMessageArray, RA.partitionMap(checkOkNg_FromMessage))
      )
    )

    console.log(result)
  })
})
```

実行結果
```ts
time : 2025-01-19T08:12:59.525Z - text: [start]
time : 2025-01-19T08:13:04.529Z - text: mapper2-index = 5
time : 2025-01-19T08:13:05.543Z - text: https://test/5/1
time : 2025-01-19T08:13:07.557Z - text: https://test/5/2 - ng - 2000 - Sleep NG after wait
time : 2025-01-19T08:13:07.558Z - text: https://test/5/3 - ng - Sleep NG before wait
time : 2025-01-19T08:13:11.571Z - text: https://test/5/4 - ng - 4000 - Sleep NG after wait
time : 2025-01-19T08:13:16.590Z - text: https://test/5/5
{ left: [], right: [ 'https://test/5 - 17000' ] }
```



### 全部同時実行からの最大２個まで同時実行 ※一部性能劣化

```plantuml
@startuml
robust "全体" as all
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
concise "https://test/5/5" as test5_5

@all
0 is 待機中
+1 is 処理中
+16 is 待機中

@test5
1 is 処理中
+5 is {hidden}

@test5_1
6 is 処理中
+1 is {hidden}

@test5_2
6 is 処理中 #pink
+2 is {hidden}

@test5_3
8 is 処理中 #pink
+0.01 is {hidden}

@test5_4
8 is 処理中 #pink
+4 is {hidden}

@test5_5
12 is 処理中
+5 is {hidden}

@enduml
```

```ts
import { describe, test } from 'vitest'
import * as TE from 'fp-ts/TaskEither'
import { pipe } from 'fp-ts/lib/function'
import * as RA from 'fp-ts/ReadonlyArray'
import * as E from 'fp-ts/Either'

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)
    }
  }) as Promise<number>

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 getLastIndex(site: string) {
  const index = site.slice(-1)
  return Number(index)
}
function getTopIndex(site: string) {
  const index = site.substring(site.length - 3, site.length - 2)
  return Number(index)
}

function getWaitNumber(index: number) {
  const wait = index * 1000
  return wait
}

function checkOkNg_FromMessage(aboutOkMessage: string) {
  const resultMessage: E.Either<string, string> =
    aboutOkMessage.indexOf('ng') > 0 ? E.left(aboutOkMessage) : E.right(aboutOkMessage)
  return resultMessage
}

const do_sleep1 = (site: string) => (wait: number) =>
  TE.tryCatch(
    () =>
      sleep(wait)
        .then((_wait) => {
          checkLog(site)
          return `${site} - ok - ${_wait}`
        })
        .catch((error) => {
          checkLog(`${site} - ng - ${error}`)
          return `${site} - ng - ${error}`
        }),
    () => 'Error 1'
  )
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 do_sleep2 = (site: string) => (wait: number) =>
  TE.tryCatch(
    () =>
      sleep(wait)
        .then((_wait) => {
          const index = getTopIndex(site)
          checkLog(`mapper2-index = ${index}`)
          return (opWait: number) => `${site.substring(0, site.length - 2)} - ${_wait + opWait}`
        })
        .catch((error) => {
          const index = getTopIndex(site)
          checkLog(`mapper2-index = ${index} - ng - ${error}`)
          return () => `${site.substring(0, site.length - 2)} - ng - ${error}`
        }),
    () => 'Error 2'
  )

const op_message1_and_2 =
  (op: (a: number, b: number) => number) =>
  ([message2, message1Array]: [
    ((opWait: number) => string) | (() => string),
    readonly string[]
  ]) => {
    const opWait = pipe(message1Array, RA.map(getWaitFromMapper1Message), RA.reduce(0, op))
    return TE.right(message2(opWait))
  }

const opStringArray = (a: readonly string[], b: readonly string[]) => {
  if (b.length == 2) {
    const b0 = getWaitFromMapper1Message(b[0])
    const b1 = getWaitFromMapper1Message(b[1])
    if (b0 > b1) {
      const result = [...a, b[0]]
      return result
    } else {
      const result = [...a, b[1]]
      return result
    }
  } else {
    const result = [...a, ...b]
    return result
  }
}

const map_TE_2_array =
  (op: (a: readonly string[], b: readonly string[]) => readonly string[]) =>
  (messageArray: TE.TaskEither<string, readonly (readonly string[])[]>) => {
    const converted_message = pipe(
      messageArray,
      TE.fold(
        (err) => TE.left(err),
        (message1Array: readonly (readonly string[])[]) => {
          const opWait = pipe(message1Array, RA.reduce([], op))
          return TE.right(opWait)
        }
      )
    )
    return converted_message
  }

const mapper1 = (site: string) => {
  const action = pipe(site, getLastIndex, getWaitNumber, do_sleep1(site))
  return action
}

function action2_and_1(
  action2: TE.TaskEither<string, ((opWait: number) => string) | (() => string)>,
  action1: TE.TaskEither<string, readonly string[]>,
  op: (a: number, b: number) => number
) {
  return pipe(
    action2,
    TE.fold(
      (error2) => TE.right(error2),
      (message2) =>
        pipe(
          message2(123),
          checkOkNg_FromMessage,
          E.fold(
            (ngMessage2) => TE.right(ngMessage2),
            () =>
              pipe(
                action1,
                TE.foldW(
                  (error1) => TE.right(error1),
                  (message1) => op_message1_and_2(op)([message2, message1])
                )
              )
          )
        )
    )
  )
}

const mapper2_limit2 = (sites: string[]) => {
  const site = sites[0]
  const action2 = pipe(site, getTopIndex, getWaitNumber, do_sleep2(site))

  const limit = 2
  const chunkSites = pipe(sites, RA.chunksOf(limit))
  const action1 = pipe(chunkSites, RA.map(RA.traverse(TE.ApplicativePar)(mapper1)))
  const flatAction1 = pipe(action1, RA.sequence(TE.ApplicativeSeq), map_TE_2_array(opStringArray))

  const action = action2_and_1(action2, flatAction1, sum)
  return action
}

describe('fp-ts 非同期処理２つの配列', () => {
  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'
      ]
    ]

    checkLog('[start]')

    const aboutOkAction = pipe(sites, RA.traverse(TE.ApplicativePar)(mapper2_limit2))
    const aboutOkResult = await aboutOkAction()
    const result = pipe(
      aboutOkResult,
      E.foldW(
        (error) => E.left(error),
        (aboutOkMessageArray: readonly string[]) =>
          pipe(aboutOkMessageArray, RA.partitionMap(checkOkNg_FromMessage))
      )
    )

    console.log(result)
  })
})
```

実行結果
```ts
time : 2025-01-19T08:18:19.837Z - text: [start]
time : 2025-01-19T08:18:24.843Z - text: mapper2-index = 5
time : 2025-01-19T08:18:25.856Z - text: https://test/5/1
time : 2025-01-19T08:18:26.856Z - text: https://test/5/2 - ng - 2000 - Sleep NG after wait
time : 2025-01-19T08:18:26.857Z - text: https://test/5/3 - ng - Sleep NG before wait
time : 2025-01-19T08:18:30.859Z - text: https://test/5/4 - ng - 4000 - Sleep NG after wait
time : 2025-01-19T08:18:35.875Z - text: https://test/5/5
{ left: [], right: [ 'https://test/5 - 16000' ] }
```


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

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

```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
concise "https://test/5/5" as test5_5

@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
6 is 処理中 #pink
+2 is {hidden}
@test5_3
6 is 処理中 #pink
+0.01 is {hidden}
@test5_4
6 is 処理中 #pink
+4 is {hidden}
@test5_5
6 is 処理中
+5 is {hidden}

@enduml
```

```ts
import { describe, test } from 'vitest'
import * as TE from 'fp-ts/TaskEither'
import { pipe } from 'fp-ts/lib/function'
import * as RA from 'fp-ts/ReadonlyArray'
import * as E from 'fp-ts/Either'

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)
    }
  }) as Promise<number>

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

const testOptions = {
  timeout: 1000 * 100
}

function getLastIndex(site: string) {
  const index = site.slice(-1)
  return Number(index)
}
function getTopIndex(site: string) {
  const index = site.substring(site.length - 3, site.length - 2)
  return Number(index)
}

function getWaitNumber(index: number) {
  const wait = index * 1000
  return wait
}

function checkOkNg_FromMessage(aboutOkMessage: string) {
  const resultMessage: E.Either<string, string> =
    aboutOkMessage.indexOf('ng') > 0 ? E.left(aboutOkMessage) : E.right(aboutOkMessage)
  return resultMessage
}

const do_sleep1 = (site: string) => (wait: number) =>
  TE.tryCatch(
    () =>
      sleep(wait)
        .then((_wait) => {
          checkLog(site)
          return `${site} - ok - ${_wait}`
        })
        .catch((error) => {
          checkLog(`${site} - ng - ${error}`)
          return `${site} - ng - ${error}`
        }),
    () => 'Error 1'
  )
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 do_sleep2 = (site: string) => (wait: number) =>
  TE.tryCatch(
    () =>
      sleep(wait)
        .then((_wait) => {
          const index = getTopIndex(site)
          checkLog(`mapper2-index = ${index}`)
          return (opWait: number) => `${site.substring(0, site.length - 2)} - ${_wait + opWait}`
        })
        .catch((error) => {
          const index = getTopIndex(site)
          checkLog(`mapper2-index = ${index} - ng - ${error}`)
          return () => `${site.substring(0, site.length - 2)} - ng - ${error}`
        }),
    () => 'Error 2'
  )

const op_message1_and_2 =
  (op: (a: number, b: number) => number) =>
  ([message2, message1Array]: [
    ((opWait: number) => string) | (() => string),
    readonly string[]
  ]) => {
    const opWait = pipe(message1Array, RA.map(getWaitFromMapper1Message), RA.reduce(0, op))
    return TE.right(message2(opWait))
  }

const mapper1 = (site: string) => {
  const action = pipe(site, getLastIndex, getWaitNumber, do_sleep1(site))
  return action
}

function action2_and_1(
  action2: TE.TaskEither<string, ((opWait: number) => string) | (() => string)>,
  action1: TE.TaskEither<string, readonly string[]>,
  op: (a: number, b: number) => number
) {
  return pipe(
    action2,
    TE.fold(
      (error2) => TE.right(error2),
      (message2) =>
        pipe(
          message2(123),
          checkOkNg_FromMessage,
          E.fold(
            (ngMessage2) => TE.right(ngMessage2),
            () =>
              pipe(
                action1,
                TE.foldW(
                  (error1) => TE.right(error1),
                  (message1) => op_message1_and_2(op)([message2, message1])
                )
              )
          )
        )
    )
  )
}

const mapper2_par = (sites: string[]) => {
  const site = sites[0]
  const action2 = pipe(site, getTopIndex, getWaitNumber, do_sleep2(site))
  const action1 = pipe(sites, RA.traverse(TE.ApplicativePar)(mapper1))
  const action = action2_and_1(action2, action1, Math.max)
  return action
}

describe('fp-ts 非同期処理２つの配列', () => {
  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'
      ]
    ]

    checkLog('[start]')

    const aboutOkAction = pipe(sites, RA.traverse(TE.ApplicativePar)(mapper2_par))
    const aboutOkResult = await aboutOkAction()
    const result = pipe(
      aboutOkResult,
      E.foldW(
        (error) => E.left(error),
        (aboutOkMessageArray: readonly string[]) =>
          pipe(aboutOkMessageArray, RA.partitionMap(checkOkNg_FromMessage))
      )
    )

    console.log(result)
  })
})
```

実行結果
```ts
time : 2025-01-19T08:23:54.146Z - text: [start]
time : 2025-01-19T08:23:54.149Z - text: mapper2-index = 3 - ng - Sleep NG before wait
time : 2025-01-19T08:23:55.150Z - text: mapper2-index = 1
time : 2025-01-19T08:23:56.156Z - text: mapper2-index = 2 - ng - 2000 - Sleep NG after wait
time : 2025-01-19T08:23:56.157Z - text: https://test/1/1
time : 2025-01-19T08:23:58.160Z - text: mapper2-index = 4 - ng - 4000 - Sleep NG after wait
time : 2025-01-19T08:23:59.163Z - text: mapper2-index = 5
time : 2025-01-19T08:23:59.165Z - text: https://test/5/3 - ng - Sleep NG before wait
time : 2025-01-19T08:24:00.178Z - text: https://test/5/1
time : 2025-01-19T08:24:01.180Z - text: https://test/5/2 - ng - 2000 - Sleep NG after wait
time : 2025-01-19T08:24:03.167Z - text: https://test/5/4 - ng - 4000 - Sleep NG after wait
time : 2025-01-19T08:24:04.166Z - text: https://test/5/5
{
  left: [
    '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'
  ],
  right: [ 'https://test/1 - 2000', 'https://test/5 - 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
concise "https://test/5/5" as test5_5

@all
0 is 待機中
+1 is 処理中
+17 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}
@test5_5
13 is 処理中
+5 is {hidden}

@enduml
```

```ts
import { describe, test } from 'vitest'
import * as TE from 'fp-ts/TaskEither'
import { pipe } from 'fp-ts/lib/function'
import * as RA from 'fp-ts/ReadonlyArray'
import * as E from 'fp-ts/Either'

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)
    }
  }) as Promise<number>

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 getLastIndex(site: string) {
  const index = site.slice(-1)
  return Number(index)
}
function getTopIndex(site: string) {
  const index = site.substring(site.length - 3, site.length - 2)
  return Number(index)
}

function getWaitNumber(index: number) {
  const wait = index * 1000
  return wait
}

function checkOkNg_FromMessage(aboutOkMessage: string) {
  const resultMessage: E.Either<string, string> =
    aboutOkMessage.indexOf('ng') > 0 ? E.left(aboutOkMessage) : E.right(aboutOkMessage)
  return resultMessage
}

const do_sleep1 = (site: string) => (wait: number) =>
  TE.tryCatch(
    () =>
      sleep(wait)
        .then((_wait) => {
          checkLog(site)
          return `${site} - ok - ${_wait}`
        })
        .catch((error) => {
          checkLog(`${site} - ng - ${error}`)
          return `${site} - ng - ${error}`
        }),
    () => 'Error 1'
  )
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 do_sleep2 = (site: string) => (wait: number) =>
  TE.tryCatch(
    () =>
      sleep(wait)
        .then((_wait) => {
          const index = getTopIndex(site)
          checkLog(`mapper2-index = ${index}`)
          return (opWait: number) => `${site.substring(0, site.length - 2)} - ${_wait + opWait}`
        })
        .catch((error) => {
          const index = getTopIndex(site)
          checkLog(`mapper2-index = ${index} - ng - ${error}`)
          return () => `${site.substring(0, site.length - 2)} - ng - ${error}`
        }),
    () => 'Error 2'
  )

const op_message1_and_2 =
  (op: (a: number, b: number) => number) =>
  ([message2, message1Array]: [
    ((opWait: number) => string) | (() => string),
    readonly string[]
  ]) => {
    const opWait = pipe(message1Array, RA.map(getWaitFromMapper1Message), RA.reduce(0, op))
    return TE.right(message2(opWait))
  }

const mapper1 = (site: string) => {
  const action = pipe(site, getLastIndex, getWaitNumber, do_sleep1(site))
  return action
}

function action2_and_1(
  action2: TE.TaskEither<string, ((opWait: number) => string) | (() => string)>,
  action1: TE.TaskEither<string, readonly string[]>,
  op: (a: number, b: number) => number
) {
  return pipe(
    action2,
    TE.fold(
      (error2) => TE.right(error2),
      (message2) =>
        pipe(
          message2(123),
          checkOkNg_FromMessage,
          E.fold(
            (ngMessage2) => TE.right(ngMessage2),
            () =>
              pipe(
                action1,
                TE.foldW(
                  (error1) => TE.right(error1),
                  (message1) => op_message1_and_2(op)([message2, message1])
                )
              )
          )
        )
    )
  )
}

const mapper2_seq = (sites: string[]) => {
  const site = sites[0]
  const action2 = pipe(site, getTopIndex, getWaitNumber, do_sleep2(site))
  const action1 = pipe(sites, RA.traverse(TE.ApplicativeSeq)(mapper1))
  const action = action2_and_1(action2, action1, sum)
  return action
}

describe('fp-ts 非同期処理２つの配列', () => {
  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'
      ]
    ]

    checkLog('[start]')

    const aboutOkAction = pipe(sites, RA.traverse(TE.ApplicativePar)(mapper2_seq))
    const aboutOkResult = await aboutOkAction()
    const result = pipe(
      aboutOkResult,
      E.foldW(
        (error) => E.left(error),
        (aboutOkMessageArray: readonly string[]) =>
          pipe(aboutOkMessageArray, RA.partitionMap(checkOkNg_FromMessage))
      )
    )

    console.log(result)
  })
})
```

実行結果
```ts
time : 2025-01-19T08:27:39.259Z - text: [start]
time : 2025-01-19T08:27:39.262Z - text: mapper2-index = 3 - ng - Sleep NG before wait
time : 2025-01-19T08:27:40.274Z - text: mapper2-index = 1
time : 2025-01-19T08:27:41.273Z - text: mapper2-index = 2 - ng - 2000 - Sleep NG after wait
time : 2025-01-19T08:27:41.289Z - text: https://test/1/1
time : 2025-01-19T08:27:43.273Z - text: mapper2-index = 4 - ng - 4000 - Sleep NG after wait
time : 2025-01-19T08:27:44.264Z - text: mapper2-index = 5
time : 2025-01-19T08:27:45.273Z - text: https://test/5/1
time : 2025-01-19T08:27:47.290Z - text: https://test/5/2 - ng - 2000 - Sleep NG after wait
time : 2025-01-19T08:27:47.292Z - text: https://test/5/3 - ng - Sleep NG before wait
time : 2025-01-19T08:27:51.297Z - text: https://test/5/4 - ng - 4000 - Sleep NG after wait
time : 2025-01-19T08:27:56.298Z - text: https://test/5/5
{
  left: [
    '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'
  ],
  right: [ 'https://test/1 - 2000', 'https://test/5 - 17000' ]
}
```



### 全部同時実行からの最大２個まで同時実行 ※一部性能劣化

```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
concise "https://test/5/5" as test5_5

@all
0 is 待機中
+1 is 処理中
+16 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
8 is 処理中 #pink
+0.01 is {hidden}
@test5_4
8 is 処理中 #pink
+4 is {hidden}
@test5_5
12 is 処理中
+5 is {hidden}

@enduml
```

```ts
import { describe, test } from 'vitest'
import * as TE from 'fp-ts/TaskEither'
import { pipe } from 'fp-ts/lib/function'
import * as RA from 'fp-ts/ReadonlyArray'
import * as E from 'fp-ts/Either'

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)
    }
  }) as Promise<number>

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 getLastIndex(site: string) {
  const index = site.slice(-1)
  return Number(index)
}
function getTopIndex(site: string) {
  const index = site.substring(site.length - 3, site.length - 2)
  return Number(index)
}

function getWaitNumber(index: number) {
  const wait = index * 1000
  return wait
}

function checkOkNg_FromMessage(aboutOkMessage: string) {
  const resultMessage: E.Either<string, string> =
    aboutOkMessage.indexOf('ng') > 0 ? E.left(aboutOkMessage) : E.right(aboutOkMessage)
  return resultMessage
}

const do_sleep1 = (site: string) => (wait: number) =>
  TE.tryCatch(
    () =>
      sleep(wait)
        .then((_wait) => {
          checkLog(site)
          return `${site} - ok - ${_wait}`
        })
        .catch((error) => {
          checkLog(`${site} - ng - ${error}`)
          return `${site} - ng - ${error}`
        }),
    () => 'Error 1'
  )
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 do_sleep2 = (site: string) => (wait: number) =>
  TE.tryCatch(
    () =>
      sleep(wait)
        .then((_wait) => {
          const index = getTopIndex(site)
          checkLog(`mapper2-index = ${index}`)
          return (opWait: number) => `${site.substring(0, site.length - 2)} - ${_wait + opWait}`
        })
        .catch((error) => {
          const index = getTopIndex(site)
          checkLog(`mapper2-index = ${index} - ng - ${error}`)
          return () => `${site.substring(0, site.length - 2)} - ng - ${error}`
        }),
    () => 'Error 2'
  )

const op_message1_and_2 =
  (op: (a: number, b: number) => number) =>
  ([message2, message1Array]: [
    ((opWait: number) => string) | (() => string),
    readonly string[]
  ]) => {
    const opWait = pipe(message1Array, RA.map(getWaitFromMapper1Message), RA.reduce(0, op))
    return TE.right(message2(opWait))
  }

const opStringArray = (a: readonly string[], b: readonly string[]) => {
  if (b.length == 2) {
    const b0 = getWaitFromMapper1Message(b[0])
    const b1 = getWaitFromMapper1Message(b[1])
    if (b0 > b1) {
      const result = [...a, b[0]]
      return result
    } else {
      const result = [...a, b[1]]
      return result
    }
  } else {
    const result = [...a, ...b]
    return result
  }
}

const map_TE_2_array =
  (op: (a: readonly string[], b: readonly string[]) => readonly string[]) =>
  (messageArray: TE.TaskEither<string, readonly (readonly string[])[]>) => {
    const converted_message = pipe(
      messageArray,
      TE.fold(
        (err) => TE.left(err),
        (message1Array: readonly (readonly string[])[]) => {
          const opWait = pipe(message1Array, RA.reduce([], op))
          return TE.right(opWait)
        }
      )
    )
    return converted_message
  }

const mapper1 = (site: string) => {
  const action = pipe(site, getLastIndex, getWaitNumber, do_sleep1(site))
  return action
}

function action2_and_1(
  action2: TE.TaskEither<string, ((opWait: number) => string) | (() => string)>,
  action1: TE.TaskEither<string, readonly string[]>,
  op: (a: number, b: number) => number
) {
  return pipe(
    action2,
    TE.fold(
      (error2) => TE.right(error2),
      (message2) =>
        pipe(
          message2(123),
          checkOkNg_FromMessage,
          E.fold(
            (ngMessage2) => TE.right(ngMessage2),
            () =>
              pipe(
                action1,
                TE.foldW(
                  (error1) => TE.right(error1),
                  (message1) => op_message1_and_2(op)([message2, message1])
                )
              )
          )
        )
    )
  )
}

const mapper2_limit2 = (sites: string[]) => {
  const site = sites[0]
  const action2 = pipe(site, getTopIndex, getWaitNumber, do_sleep2(site))

  const limit = 2
  const chunkSites = pipe(sites, RA.chunksOf(limit))
  const action1 = pipe(chunkSites, RA.map(RA.traverse(TE.ApplicativePar)(mapper1)))
  const flatAction1 = pipe(action1, RA.sequence(TE.ApplicativeSeq), map_TE_2_array(opStringArray))

  const action = action2_and_1(action2, flatAction1, sum)
  return action
}

describe('fp-ts 非同期処理２つの配列', () => {
  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'
      ]
    ]

    checkLog('[start]')

    const aboutOkAction = pipe(sites, RA.traverse(TE.ApplicativePar)(mapper2_limit2))
    const aboutOkResult = await aboutOkAction()
    const result = pipe(
      aboutOkResult,
      E.foldW(
        (error) => E.left(error),
        (aboutOkMessageArray: readonly string[]) =>
          pipe(aboutOkMessageArray, RA.partitionMap(checkOkNg_FromMessage))
      )
    )

    console.log(result)
  })
})
```

実行結果
```ts
time : 2025-01-19T08:32:06.335Z - text: [start]
time : 2025-01-19T08:32:06.339Z - text: mapper2-index = 3 - ng - Sleep NG before wait
time : 2025-01-19T08:32:07.354Z - text: mapper2-index = 1
time : 2025-01-19T08:32:08.340Z - text: mapper2-index = 2 - ng - 2000 - Sleep NG after wait
time : 2025-01-19T08:32:08.357Z - text: https://test/1/1
time : 2025-01-19T08:32:10.345Z - text: mapper2-index = 4 - ng - 4000 - Sleep NG after wait
time : 2025-01-19T08:32:11.343Z - text: mapper2-index = 5
time : 2025-01-19T08:32:12.353Z - text: https://test/5/1
time : 2025-01-19T08:32:13.349Z - text: https://test/5/2 - ng - 2000 - Sleep NG after wait
time : 2025-01-19T08:32:13.351Z - text: https://test/5/3 - ng - Sleep NG before wait
time : 2025-01-19T08:32:17.361Z - text: https://test/5/4 - ng - 4000 - Sleep NG after wait
time : 2025-01-19T08:32:22.368Z - text: https://test/5/5
{
  left: [
    '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'
  ],
  right: [ 'https://test/1 - 2000', 'https://test/5 - 16000' ]
}
```