👻

同時実行制限付きPromise Runnerを提供するhooks

2022/11/20に公開

同時実行制限付きPromise Runner

的なものがたまに欲しくなるのでコピペできる用に実装したものを置いておく。
たとえば数十枚の画像を順番にアップロードしたいが、同時に実行するPromiseを5個に制限したく、かつアップロード完了したものは完了したことをサムネイルに表示したい、みたいなユースケースで便利

const useTaskQueue = (options_: { semaphoreSize: number }) => {
  const [state, setState] = useState<boolean[]>([]);
  const [taskQueue, setTaskQueue] = useState<{
    queue: number[];
    semaphore: number;
  }>({
    queue: [],
    semaphore: 0,
  });
  const isRunning = taskQueue.queue.length > 0;
  const promise = useRef<(index: number) => Promise<void>>();
  const options = useRef(options_);

  const runQueue = useCallback(async () => {
    if (taskQueue.queue.length === 0) {
      return;
    }
    if (taskQueue.semaphore >= options.current.semaphoreSize) {
      return;
    }

    const [task, ...rest] = taskQueue.queue;
    setTaskQueue((prev) => {
      return {
        queue: rest,
        semaphore: prev.semaphore + 1,
      };
    });
    setState((prev) => {
      const current = [...prev];
      current[task] = false;
      return current;
    });

    await promise.current?.(task);

    setState((prev) => {
      const current = [...prev];
      current[task] = true;
      return current;
    });
    setTaskQueue((prev) => {
      return {
        queue: prev.queue,
        semaphore: prev.semaphore - 1,
      };
    });
  }, [taskQueue]);

  useEffect(() => {
    if (isRunning) {
      runQueue();
    }
  }, [isRunning, runQueue]);

  return {
    start: (tasks: boolean[], do_: (index: number) => Promise<void>) => {
      setState(tasks);
      setTaskQueue({
        queue: tasks.map((_, i) => i),
        semaphore: 0,
      });
      promise.current = do_;
    },
    state,
  };
};

使用例

const { start, state } = useTaskQueue({ semaphoreSize: 3 });

<button
  onClick={async () => {
    start(tasks.map((_, i) => false), async (index: number) => {
      // ここで実行タスクを定義する
    });
  }}
>
  START A JOB
</button>

注意点

  • stateが単にbooleanの配列になっているが、これは元々管理したいデータを持たせてisFinishedをそこに追加する、みたいにできるようにするほうが便利な気はしている。のでその辺はユースケースに合わせて改造推奨
  • 同じく、Promiseが値を返すケースでもstateに入れるようにしていい気はしている。
  • パフォーマンスのチェックとか何もしてないので無駄レンダリングが走るかも
  • もうちょっと色々使ったりして真に便利になったらライブラリにしてpublishしたい気持ち

Discussion