0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PromiseからSuspenseに変換する

Last updated at Posted at 2024-07-10

概要

Reactを使っていると、PromiseではなくSuspenseにしたいときが度々ありますが、都度変換のコードを書くのが面倒になったので汎用的な変換関数を作りました。

コード

import { useEffect, useRef } from "react";

/**
 * キーを定義したい場合、こちらに追記してください
 * 定義したキーのみ受け付けたい場合、 |から後ろを消してください
 */
type Key = 'test' | (string & {});

const result = new Map<Key, {
  timeoutId: number;
  value: unknown;
}>();
const waiting = new Map<Key, Promise<unknown>>;

type PromiseResult<FnType extends (...args: unknown[]) => Promise<unknown>> = Awaited<
  Promise<ReturnType<FnType>>
>;

/**
 * コンポーネントが破棄されたとき、cacheTime以内に同一のキーのコンポーネントがこない場合キャッシュを消します
 * cacheTimeを負の数にした場合、即座にキャッシュを消します
 */
export const promiseToSuspense = <T extends () => Promise<unknown>>(key: Key, promise: T, cacheTime = 100): PromiseResult<T> => {
  useEffect(() => {
    const cr = result.get(key);
    // 同一のキーのコンポーネントが来たので、キャッシュ削除をキャンセル
    if (cr && cr.timeoutId !== 0) {
      window.clearTimeout(cr.timeoutId);
      result.set(key, { timeoutId: 0, value: cr.value });
    }
    return () => {
      // cacheTimeに応じてキャッシュを消す
      if (cacheTime >= 0) {
        result.set(key, {
          timeoutId: window.setTimeout(() => {
            result.delete(key);
          }, cacheTime),
          value: result.get(key)?.value,
        });
      } else {
        result.delete(key);
      }
    }
  }, [cacheTime, key]);
  
  // キーが変更されたとき、前のキーのキャッシュを消す
  const keyRef = useRef(key);
  if (keyRef.current !== key) {
    result.delete(key);
    keyRef.current = key;
  }

  // キャッシュがあるか、promiseが解決されていない場合新しくpromiseを呼ばない
  const r = result.get(key);
  if (r) return r.value as PromiseResult<T>;
  if (waiting.has(key)) throw waiting.get(key);

  // promiseを呼び、結果をキャッシュする
  const p = promise();
  p.then(value => {
    result.set(key, { timeoutId: 0, value });
  }).finally(() => {
    waiting.delete(key);
  });
  waiting.set(key, p);
  throw p;
}

サンプル

const SampleComponent = () => {
  const promise = new Promise<boolean>((resolve) => setTimeout(() => resolve(true), 5000));
  const result = awaitToSuspense('test', () => promise);
  return <div>{result}</div>;
}

const ParentComponent = () => 
  <Suspense>
    <SampleComponent />
  </Suspense>;
0
1
0

Register as a new user and use Qiita more conveniently

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?