2차 공부/TIL

24.08.26 Next JS 병렬 fetching / 에러발생페이지

공대탈출 2024. 8. 26. 16:12
import { API_URL } from "../../../(home)/page";

async function getMovie(id: string) {
    console.log(`Fetching movies: ${Date.now()}`);
    const response = await fetch(`${API_URL}/${id}`);
    console.log(`Finished movies: ${Date.now()}`);
    return response.json();
}

async function getVideos(id: string) {
    console.log(`Fetching videos: ${Date.now()}`);
    const response = await fetch(`${API_URL}/${id}/videos`);
    console.log(`Finished videos: ${Date.now()}`);
    return response.json();
}

export default async function MovieDetail({ params: { id } }: { params: { id: string } }) {
    const movie = await getMovie(id);
    const videos = await getVideos(id);
    return <h1>{movie.title}</h1>;
}

이전에 서버에서 fetching하는 것을 이용하여 여러 요청을 보내보았다.

 

이 방법은 데이터 요청 2개를 직렬으로 연결하여 최대시간이 걸리게 된다.

import { API_URL } from "../../../(home)/page";

async function getMovie(id: string) {
    console.time("Fetching movies");
    await new Promise((resolve) => setTimeout(resolve, 5000));
    const response = await fetch(`${API_URL}/${id}`);
    console.timeEnd("Fetching movies");
    return response.json();
}

async function getVideos(id: string) {
    console.time("Fetching videos");
    await new Promise((resolve) => setTimeout(resolve, 1000));
    const response = await fetch(`${API_URL}/${id}/videos`);
    console.timeEnd("Fetching videos");
    return response.json();
}

export default async function MovieDetail({ params: { id } }: { params: { id: string } }) {
    const movie = await getMovie(id);
    const videos = await getVideos(id);
    return <h1>{movie.title}</h1>;
}

극단적인 예로 영화데이터는 5초가 걸리고 영상데이터는 1초가 걸리는 요청이라고 가정해보자.

두가지 요청을 완수하고 사용자에게 정상적인 페이지를 보여주기까지 6초가 걸리는 것이다.

따라서 우리는 병렬적으로 fetching하기위해 Promise.all을 사용해보자.

 

import { API_URL } from "../../../(home)/page";

async function getMovie(id: string) {
    console.time("Fetching movies");
    await new Promise((resolve) => setTimeout(resolve, 5000));
    const response = await fetch(`${API_URL}/${id}`);
    console.timeEnd("Fetching movies");
    return response.json();
}

async function getVideos(id: string) {
    console.time("Fetching videos");
    await new Promise((resolve) => setTimeout(resolve, 1000));
    const response = await fetch(`${API_URL}/${id}/videos`);
    console.timeEnd("Fetching videos");
    return response.json();
}

export default async function MovieDetail({ params: { id } }: { params: { id: string } }) {
    console.log("-------");
    console.log("start fetching");
    const [movie, videos] = await Promise.all([getMovie(id), getVideos(id)]);
    console.log("finished fetching");
    return <h1>{movie.title}</h1>;
}

콘솔로그만 본다면 6초가 걸렸을 것이라고 생각이 들 수도 있지만, 두 요청을 한번에 보내 5.013초가 최종적으로 걸린 것이다.


import { API_URL } from "../app/(home)/page";

async function getMovie(id: string) {
    console.time("Fetching movies");
    await new Promise((resolve) => setTimeout(resolve, 5000));
    const response = await fetch(`${API_URL}/${id}`);
    console.timeEnd("Fetching movies");
    return response.json();
}

export default async function MovieInfo({ id }: { id: string }) {
    const movie = await getMovie(id);
    return <h6>{JSON.stringify(movie)}</h6>;
}
import { API_URL } from "../app/(home)/page";

async function getVideos(id: string) {
    console.time("Fetching videos");
    await new Promise((resolve) => setTimeout(resolve, 3000));
    const response = await fetch(`${API_URL}/${id}/videos`);
    console.timeEnd("Fetching videos");
    return response.json();
}

export default async function MovieVideos({ id }: { id: string }) {
    const videos = await getVideos(id);
    return <h6>{JSON.stringify(videos)}</h6>;
}

이렇게 요청하고 데이터를 보여주는 async 컴포넌트를 만들어준다.

import { Suspense } from "react";
import MovieInfo from "../../../../components/movie-info";
import MovieVideos from "../../../../components/movie-videos";

export default async function MovieDetail({ params: { id } }: { params: { id: string } }) {
    return (
        <div>
            <Suspense fallback={<h1>Loading Movie Info</h1>}>
                <MovieInfo id={id} />
            </Suspense>
            <Suspense fallback={<h1>Loading Movie Video</h1>}>
                <MovieVideos id={id} />
            </Suspense>
        </div>
    );
}

그 컴포넌트들을 각각 react의 Suspense로 감싸주고, fallback 속성에 해당 컴포넌트가 로딩중일 때 보여줄 컴포넌트를 넣어준다.

Promise.all을 사용하면 가장 긴 요청이 끝나고서야 loading.tsx 컴포넌트가 사라지고, 원하는 page컴포넌트가 화면에 출려되었는데, 이렇게 Suspense를 사용하면 각각의 요청이 끝날때마다 데이터를 화면에 보여줄 수 있어 TTV를 빠르게 만들 수 있다.


 

import { API_URL } from "../app/(home)/page";

async function getVideos(id: string) {
    await new Promise((resolve) => setTimeout(resolve, 3000));
    throw new Error("somthing Broken!");
    // const response = await fetch(`${API_URL}/${id}/videos`);
    // return response.json();
}

export default async function MovieVideos({ id }: { id: string }) {
    const videos = await getVideos(id);
    return <h6>{JSON.stringify(videos)}</h6>;
}

영화 영상을 받아오는 함수에서 에러가 난다고 가정해보자.

유저에게 이런 화면을 보여줄 필요는 없지 않을까?

물론 저 화면은 dev환경에서 보이는 것이고, 유저에겐 흰 화면만 보일 것이다.

따라서 우리는 에러상황의 예외처리를 해야한다.

error이라는 파일을 해당 페이지 폴더에 만들어주자.

그리고 use client는 꼭 작성해야한다.

에러발생시 페이지