sprout2000
@sprout2000

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

[JS/TS] 非同期のエラーハンドリング、あなたはどちら派?

アンケート

お聞かせください!
JavaScript/TypeScript の非同期処理でのエラーハンドリングにおいて、あなたは try/catch (async/await) or then/catch のどちらを使うべきだとお考えですか?
良ければその理由もお教えいただけますと幸いです。

例) よくあるスライドショーのための React カスタムフックにて

  • try/catch
const useSlideshow = (query: string, interval: number) => {
  const [url, setUrl] = useState(query);

  useEffect(() => {
    const timerId = setInterval(() => {
      const effect = async () => {
        try {
          const res = await fetch(query);

          if (document.startViewTransition) {
            document.startViewTransition(() => setUrl(res.url));
          } else {
            setUrl(res.url);
          }
        } catch (err) {
          console.error(`${err}`);
        }
      };

      effect();
    }, interval);

    return () => {
      clearInterval(timerId);
    };
  }, [interval, query]);

  return [url] as const;
};
  • then/catch(相違部分のみ抜粋)
    const timerId = setInterval(() => {
      fetch(query)
        .then((res) => {
          if (document.startViewTransition) {
            document.startViewTransition(() => setUrl(res.url));
          } else {
            setUrl(res.url);
          }
        })
        .catch((err) => {
          console.error(`${err}`);
        });
    }, interval);

個人的には then/catch の方が直感的に読めるような気がするのですが...

ちなみに Vitest でのカバレッジ差

  • try/catch
    2023-04-06-151826.png

  • then/catch
    2023-04-06-152358.png

ChatGPT さんがちゃっちゃと書いてくれたテスト(クリックで展開)
import "@testing-library/jest-dom";
import { act, render, screen } from "@testing-library/react";
import { useSlideshow } from "../useSlideshow";

vi.useFakeTimers();

const query = "https://unsplash.com/photos/bIhpiQA009k";

describe("useSlideshow", () => {
  beforeEach(() => {
    vi.clearAllTimers();
    vi.clearAllMocks();
  });

  it("should update the url after specified interval", async () => {
    const mockFetch = Promise.resolve({
      url: query,
    });

    global.fetch = vi.fn().mockResolvedValueOnce(mockFetch);

    function MockComponent() {
      const [url] = useSlideshow(query, 1000);
      return <div>{url}</div>;
    }

    render(<MockComponent />);
    await act(async () => {
      vi.advanceTimersByTime(1000);
    });

    expect(screen.getByText(query)).toBeInTheDocument();
  });

  it("should catch error and log it to the console", async () => {
    const consoleSpy = vi.spyOn(console, "error");

    global.fetch = vi.fn().mockRejectedValueOnce(new Error("Fetch error"));

    function MockComponent() {
      const [url] = useSlideshow(query, 1000);
      return <div>{url}</div>;
    }

    render(<MockComponent />);
    await act(async () => {
      vi.advanceTimersByTime(1000);
    });

    expect(consoleSpy).toHaveBeenCalledWith("Error: Fetch error");
    consoleSpy.mockRestore();
  });
});
0

同じくthen/catchのほうがいいと思いますね!
if (document.startViewTransition) { ~ の処理が非同期処理に成功したときだけ実行される、ということがより明示的だと思います。

1Like

@takahiro1227 さん
ご回答ありがとうございます。おっしゃる通り、非同期処理の成否がより明示的だと思いますし、視覚的にも流れが明確であるような気がします。

ちなみに、さらに ChatGPT さんを追い込んだらカバレッジ 100% になりました😃

スクリーンショット 2023-05-05 16.08.12.png

ChatGPT さんがちゃっちゃと書いてくれたテスト(クリックで展開)
import "@testing-library/jest-dom";
import { act, render, screen } from "@testing-library/react";
import { useSlideshow } from "../useSlideshow";

vi.useFakeTimers();

const query = "https://unsplash.com/photos/bIhpiQA009k";

describe("useSlideshow", () => {
  beforeEach(() => {
    vi.clearAllTimers();
    vi.clearAllMocks();
    delete document.startViewTransition;
  });

  afterEach(() => {
    delete document.startViewTransition;
  });

  it("should update the url after specified interval without transition", async () => {
    const mockFetch = Promise.resolve({
      url: query,
    });

    global.fetch = vi.fn().mockResolvedValueOnce(mockFetch);

    function MockComponent() {
      const [url] = useSlideshow(query, 1000);
      return <div>{url}</div>;
    }

    render(<MockComponent />);
    await act(async () => {
      vi.advanceTimersByTime(1000);
    });

    expect(screen.getByText(query)).toBeInTheDocument();
    expect(document.startViewTransition).toBeUndefined();
  });

  it("should update the url after specified interval with transition", async () => {
    document.startViewTransition = vi.fn().mockImplementationOnce((cb) => {
      cb();
    });

    const mockFetch = Promise.resolve({
      url: query,
    });

    global.fetch = vi.fn().mockResolvedValueOnce(mockFetch);

    function MockComponent() {
      const [url] = useSlideshow(query, 1000);
      return <div>{url}</div>;
    }

    render(<MockComponent />);
    await act(async () => {
      vi.advanceTimersByTime(1000);
    });

    expect(screen.getByText(query)).toBeInTheDocument();
    expect(document.startViewTransition).toHaveBeenCalledTimes(1);
  });

  it("should catch error and log it to the console", async () => {
    const consoleSpy = vi.spyOn(console, "error");

    global.fetch = vi.fn().mockRejectedValueOnce(new Error("Fetch error"));

    function MockComponent() {
      const [url] = useSlideshow(query, 1000);
      return <div>{url}</div>;
    }

    render(<MockComponent />);
    await act(async () => {
      vi.advanceTimersByTime(1000);
    });

    expect(consoleSpy).toHaveBeenCalledWith("Error: Fetch error");
    consoleSpy.mockRestore();
  });
});
1Like

Your answer might help someone💌