NextJs (6) Parallel Requests : Promise.all & Suspense

์ฑ„์˜๋ฏผยท2024๋…„ 2์›” 15์ผ

๐Ÿ“š Parallel Requests

๐Ÿ’ก ๋‘ ๊ฐœ์˜ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋กœ๋ถ€ํ„ฐ fetchํ•ด์˜ค๋Š” ๋ฐฉ์‹์„ ์ตœ์ ํ™” ํ•˜๋Š” ๋ฐฉ๋ฒ•.

โœ… ํ•œ ๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋งŒ fetch ํ•œ ์ƒํ™ฉ

  • ํ™ˆํŽ˜์ด์ง€์— ๋ฐ์ดํ„ฐ๋ฅผ fetch ํ•ด์˜จ ํ›„ ์ฝ”๋“œ
// app/(home)/page.tsx

async function getMovies() {
  const response = await fetch(API_URL);
  const json = await response.json();
  return json;
}

export default async function HomePage() {
  const movies = await getMovies();
  return (
    <div>
      {movies.map((movie) => (
        <li key={movie.id}>
          <Link href={`/movies/${movie.id}`}>{movies.title}</Link>
        </li>
      ))}
    </div>
  );
}

โœ… ๋‘ ๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ fetch ํ•œ ์ƒํ™ฉ

  • ์œ„ ํ™ˆํŽ˜์ด์ง€์—์„œ ๋งํฌ๋ฅผ ํด๋ฆญํ•œ ๊ฒฝ์šฐ, ๋ฐ์ดํ„ฐ๋ฅผ fetch ํ•ด์˜จ ํ›„ ์˜ํ™” ์„ธ๋ถ€์‚ฌํ•ญ ๋ณด์—ฌ์ฃผ๊ธฐ
// app/(movies)/movies/[id]/page.tsx

async function getMovie(id: string) {
  console.log(`Fetching movie with id: ${Date.now()}`); // ์†Œ์š” ์‹œ๊ฐ„์„ ์ฒดํฌํ•˜๊ธฐ ์œ„ํ•œ ์ฝ”๋“œ
  await new Promise((resolve) => setTimeout(resolve, 2000)); // ํ•จ์ˆ˜๋ฅผ 5์ดˆ๊ฐ„ ๋”œ๋ ˆ์ด ํ•˜๋Š” ์ฝ”๋“œ
  const response = await fetch(`${API_URL}/${id}`);
  return response.json();
}

async function getVideos(id: string) {
  console.log(`Fetching movie with id: ${Date.now()}`);
  await new Promise((resolve) => setTimeout(resolve, 2000));
  const response = await fetch(`${API_URL}/${id}/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 (
    <div>
      <h1> {movie.title} </h1>
    </div>
  );
}
const movie = await getMovie(id);
const videos = await getVideos(id);
  • ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด, ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋Š” ์œ„์— ์ฝ”๋“œ๋ฅผ ์‹คํ–‰์‹œํ‚ค๊ณ  ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰์‹œํ‚ค๊ธฐ ๋•Œ๋ฌธ์— ๋™๊ธฐ์ ์œผ๋กœ ์‹คํ–‰์‹œํ‚ค๊ธฐ ๋•Œ๋ฌธ์— ๋‘ ๋ฐ์ดํ„ฐ๋“ค์„ ๋ชจ๋‘ fetch ํ•ด์˜ค๋Š”๋ฐ๋Š” ์˜ค๋žœ ์‹œ๊ฐ„์ด ์†Œ์š”๋  ๊ฒƒ์ž„.
  • ์œ„ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰์‹œํ‚ฌ ๊ฒฝ์šฐ ์ถœ๋ ฅ๋˜๋Š” ๊ฐ’๋“ค, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‹œ๊ฐ„์ฐจ๋ฅผ ๋‘๋ฉฐ ๋ฐ์ดํ„ฐ๊ฐ€ fetch๋จ.

๐Ÿ“š ๋‘ ๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋™์‹œ์— ๋ณ‘๋ ฌ์ ์œผ๋กœ fetch ํ•ด์˜ค๋Š” ๋ฐฉ๋ฒ• 1. Promise.all

๐Ÿ’ก Promise.all

// app/(movies)/movies/[id]/page.tsx

export default async function MovieDetail({
  params: { id },
}: {
  params: { id: string };
}) {
  const [movie, videos] = await Promise.all([getMovie(id), getVideos(id)]);
  return (
    <div>
      <h1> {movie.title} </h1>
    </div>
  );
}
  • Promise.all ์„ ์‚ฌ์šฉํ•ด์ฃผ๋ฉด ๋‘ ๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋™์‹œ์— ๋ณ‘๋ ฌ์ ์œผ๋กœ fetchํ•˜๊ธฐ ์‹œ์ž‘ํ•œ๋‹ค.

๐Ÿ“š ๋‘ ๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋™์‹œ์— ๋ณ‘๋ ฌ์ ์œผ๋กœ fetch ํ•ด์˜ค๋Š” ๋ฐฉ๋ฒ• 2. Suspense

๐Ÿ’ก Promise.all ๋‹จ์ ?

  • ์œ„ ์ฝ”๋“œ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ํ•˜๋ฉด, getMovie(id), getVideos(id) ์œ„ ํ•จ์ˆ˜๋“ค์ด ๋ฐ์ดํ„ฐ fetch๋ฅผ ์™„๋ฃŒํ•˜๊ธฐ ์ „๊นŒ์ง€ UI(< h1 > {movie.title} < /h1 >)๊ฐ€ ๊ทธ๋ ค์ง€์ง€ ์•Š๋Š”๋‹ค.
  • ๊ทธ๋Ÿฌ๋ฏ€๋กœ, ๋ฆฌ์•กํŠธ์— Suspense, streaming ๊ธฐ๋Šฅ๋“ค์„ ์‚ฌ์šฉํ•ด์„œ, getMovie๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด ํ•ด๋‹น UI๋ฅผ ๊ทธ๋ ค๋‚ด๊ณ , getVideos๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด ํ•ด๋‹น UI๋ฅผ ๊ทธ๋ฆฌ๋„๋ก ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•  ๊ฒƒ์ž„.

๐Ÿ—ฃ๏ธ ์ง€๊ธˆ๊นŒ์ง€๋Š” page ์ปดํฌ๋„ŒํŠธ์—์„œ ๋‘ ๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋“ค์„ ๋ชจ๋‘ fetch ํ•ด์˜ค๊ณ  ์žˆ์—ˆ์Œ, ์ง€๊ธˆ๋ถ€ํ„ฐ๋Š” getMovie, getVideos๋ฅผ ๊ฐ๊ฐ์˜ ํŒŒ์ผ์—์„œ ๊ฐœ๋ณ„์ ์œผ๋กœ ๊ฐ๊ฐ render ํ•˜๊ฒŒ๋” ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•จ.

// components/movie-videos.tsx
async function getVideos(id: string) {
  console.log(`Fetching movie with id: ${Date.now()}`);
  await new Promise((resolve) => setTimeout(resolve, 2000));
  const response = await fetch(`${API_URL}/${id}/videos`);
  return response.json();
}

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

// components/movie-infos.tsx
async function getMovie(id: string) {
  console.log(`Fetching movie with id: ${Date.now()}`);
  await new Promise((resolve) => setTimeout(resolve, 2000));
  const response = await fetch(`${API_URL}/${id}`);
  return response.json();
}

export default async function MovieInfos({ id }: { id: string }) {
  const movie = await getMovie(id);
  return <h6>{JSON.stringify(movie)}</h6>;
} 
  • ๋‹ค์Œ๊ณผ ๊ฐ™์ด page์ปดํฌ๋„ŒํŠธ์—์„œ ๊ด€๋ฆฌํ•˜๋˜ fetch ํ•จ์ˆ˜๋“ค์„ ๊ฐ๊ฐ์˜ ์ปดํฌ๋„ŒํŠธ ํŒŒ์ผ๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ด€๋ฆฌ.
// app/(movies)/movies/[id]/page.tsx
export default async function MovieDetail({
  params: { id },
}: {
  params: { id: string };
}) {
  return (
    <div>
      <Suspense fallback={<h1>Loading movie info</h1>}> 
        // fallback : ๋ฐ์ดํ„ฐ๊ฐ€ fetch๋˜๋Š” ๋„์ค‘์— ๊ทธ๋ ค์ง€๋Š” UI
        <MovieInfos id={id} />
      </Suspense>
      <Suspense fallback={<h1>Loading movie videos</h1>}>
        <MovieVideos id={id} />
      </Suspense>
    </div>
  );
}
  • ๊ทธ๋ฆฌ๊ณ  ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” ๊ฐ๊ฐ์˜ ๊ฐœ๋ณ„ ์ปดํฌ๋„ŒํŠธ๋ฅผ Suspense ํ‚ค์›Œ๋“œ๋กœ ๊ฐ์‹ธ์คŒ.

  • ๊ฒฐ๊ณผ -> ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณ‘๋ ฌ์ ์œผ๋กœ ๋‘๊ฐ€์ง€๋ฅผ ๋™์‹œ์— fetch ํ•  ์ˆ˜ ์žˆ๊ณ  ํ•˜๋‚˜์˜ ์š”์ฒญ์ด ์™„๋ฃŒ๋˜๋ฉด ๊ทธ ์ฆ‰์‹œ ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๊ฐ€ render๋จ.
  • Promise.all ๊ณผ๋Š” ๋‹ค๋ฅด๊ฒŒ Suspense๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ตฌ์ฒด์ ์œผ๋กœ ํŽ˜์ด์ง€์˜ ์–ด๋А ๋ถ€๋ถ„์ด ๋กœ๋”ฉ ์ƒํƒœ์—ฌ์•ผ ํ•˜๋Š”์ง€๋ฅผ ๋ช…์‹œํ•ด ์ค„ ์ˆ˜ ์žˆ๋‹ค.
profile
ํ•ญ์ƒ ์žฌ๋ฐŒ๋Š”๊ฒƒ์„ ์ฐพ์•„ ํ—ค๋งค๋Š” ํ˜ธ๋ž‘์ด๋  ๋– ๋Œ์ด;

0๊ฐœ์˜ ๋Œ“๊ธ€