💍

カッコ良くPromise.allするやつ

2022/12/30に公開1

カッコ良いやつ

reduceの生成先の配列をpromiseの配列にしてpromise.allするやつ。
promise.allを素で使うで多分大体のケースが収まるが、このパターンならオブジェクト単位でpromiseをまとめてwaitしたい時とか、promiseを後から増やしたりしたい時にシンプルに書ける?かも。
Webpackのチャンクをロードする際にはこの方法が取られていた。

(async () => {
  const objects = {
    // ここではやってないが呼び出し先でまたarrにpromiseを突っ込めば勝手に待ってくれる
    hoge: (arr) => arr.push(wait(3), wait(4)),
    moge: (arr) => arr.push(wait(2), wait(1)),
  };
  const run = () =>
    Object.keys(objects).reduce(
      (prev, curr) => (
        (() => {
          prev.push(objects[curr](prev));
        })(),
        prev
      ),
      []
    );
  await Promise.all(run());
  console.log("end");

  async function wait(num) {
    return new Promise((a) =>
      setTimeout(() => (console.log(num), a()), num * 1000)
    );
  }
})();

Discussion

nap5nap5

オブジェクト単位でpromiseをまとめて

ここの部分少しアプローチ変えてやってみました

定義側

import { bind, bindTo } from 'fp-ts/lib/Identity'
import { pipe } from 'fp-ts/lib/function'

import { default as fetch } from "cross-fetch";
import { Either, left, right } from 'fp-ts/lib/Either';
import { chunksOf } from 'fp-ts/lib/Array';

const demoData = {
  by: "bpierre",
  descendants: 48,
  id: 37361050,
  kids: [
    37362222, 37363734, 37362079, 37362429, 37366919, 37366920, 37363171,
    37361240, 37361524, 37361316, 37364299, 37363233, 37361613, 37364963,
    37364330, 37361551, 37365637, 37362068, 37363830, 37361140, 37364871,
    37364746, 37363775,
  ],
  score: 156,
  time: 1693659009,
  title: "Semantic Zoom",
  type: "story",
  url: "https://alexanderobenauer.com/labnotes/038/",
};

type StoryData = typeof demoData;

const fetchStory = async (storyId: number): Promise<Either<Error, StoryData>> => {
  try {
    if (storyId % 2 === 0) return left(new Error("Something went wrong...", { cause: { storyId } }))
    const response = await fetch(
      `https://hacker-news.firebaseio.com/v0/item/${storyId}.json?print=pretty`
    );
    const json = await response.json();
    return right(json as StoryData);
  } catch (error) {
    return left(error as Error)
  }
};

// @see https://stackoverflow.com/a/75797074/15972569
export const niceFetch = (...storyIds: number[]) => {
  const [groupA, groupB, groupC, groupD] = pipe(storyIds, chunksOf(4))
  return pipe(
    { groupA, groupB, groupC, groupD },
    bindTo("groups"),
    bind("DoGroupA", ({ groups: { groupA } }) => Promise.all(groupA.map(fetchStory))),
    bind("DoGroupB", ({ groups: { groupB } }) => Promise.all(groupB.map(fetchStory))),
    bind("DoGroupC", ({ groups: { groupC } }) => Promise.all(groupC.map(fetchStory))),
    bind("DoGroupD", ({ groups: { groupD } }) => Promise.all(groupD.map(fetchStory))),
  )
}

使用側

import { test, expect } from "vitest";

import { niceFetch } from ".";
import { lefts, rights } from "fp-ts/lib/Array";
import { pipe } from "fp-ts/lib/function";


test("niceFetch", async () => {
  const inputStoryIds = [37367013, 37361947, 37361409, 37363362, 37358063, 37359193, 37364290,
    37366341, 37361053, 37356119, 37359310, 37362132, 37359250, 37364388,
    37349906, 37362740, 37365485, 37366678, 37364124, 37365468, 37361094,
    37364624, 37361050, 37362151,]

  const { groups, DoGroupA, DoGroupB, DoGroupC, DoGroupD } = niceFetch(...inputStoryIds)
  const batches = [DoGroupA, DoGroupB, DoGroupC, DoGroupD]
  const results = (await Promise.all(batches)).flat()
  const values = pipe(results, rights)
  const errors = pipe(results, lefts)
  const report = {
    summary: {
      okCount: values.length,
      errCount: errors.length,
    },
    detail: {
      input: { groups },
      values,
      errors
    }
  }
  expect(report).toStrictEqual({})
})

demo code.
https://codesandbox.io/p/sandbox/beautiful-colden-kfm7j6