๐ŸŒฑ <groot/>
๋ฐ˜๋ณต๋ฌธ์—์„œ API ํ˜ธ์ถœํ•˜๊ธฐ โ€” forEach๊ฐ€ ์•ˆ ๋˜๋˜ ์ด์œ , ๊ทธ๋ฆฌ๊ณ  Promise.all์˜ ์˜คํ•ด | Groot's Tech Blog
๐Ÿ‹JavaScript

๋ฐ˜๋ณต๋ฌธ์—์„œ API ํ˜ธ์ถœํ•˜๊ธฐ โ€” forEach๊ฐ€ ์•ˆ ๋˜๋˜ ์ด์œ , ๊ทธ๋ฆฌ๊ณ  Promise.all์˜ ์˜คํ•ด

2026.07.02ยท5๋ถ„ ์ฝ๊ธฐ

YouTube Playlist Generator๋ฅผ ๋งŒ๋“ค ๋•Œ, ๊ณก ๋ชฉ๋ก์„ ์ˆœํšŒํ•˜๋ฉฐ ๊ฐ ๊ณก์„ ๊ฒ€์ƒ‰ํ•˜๊ณ  ์žฌ์ƒ๋ชฉ๋ก์— ๋„ฃ๋Š” ์ฝ”๋“œ๋ฅผ ์งฐ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ forEach๋กœ ์งฐ๋”๋‹ˆ ์ด์ƒํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋˜ ์ฝ”๋“œ๊ฐ€, for...of๋กœ ๋ฐ”๊พธ๋‹ˆ ๋ฉ€์ฉกํžˆ ๋Œ์•„๊ฐ”๋‹ค. ๊ทธ๋•Œ๋Š” "๋˜๋‹ˆ๊นŒ ๋„˜์–ด๊ฐ€์ž" ํ–ˆ์ง€๋งŒ, ๋ญ๊ฐ€ ๋‹ค๋ฅธ์ง€ ์ œ๋Œ€๋กœ ํŒŒ๋ดค๋‹ค. ์˜ˆ์ „์— ์ด ์ฃผ์ œ๋กœ ๊ธ€์„ ํ•œ ๋ฒˆ ์ผ๋Š”๋ฐ, ๋‹ค์‹œ ๊ฒ€์ฆํ•ด๋ณด๋‹ˆ ๋‚ด๊ฐ€ ํ‹€๋ ธ๋˜ ๋ถ€๋ถ„๋„ ์žˆ์—ˆ๋‹ค. ์ด๋ฒˆ์—” ์‹ค์ œ๋กœ ์ฝ”๋“œ๋ฅผ ๋Œ๋ ค ํ™•์ธํ•œ ๊ฒฐ๊ณผ๋กœ ์ •๋ฆฌํ•œ๋‹ค.

๊ฒ€์ฆ์šฉ ์ฝ”๋“œ

์•„๋ž˜์ฒ˜๋Ÿผ "API ํ˜ธ์ถœ"์„ ํ‰๋‚ด ๋‚ธ๋‹ค. id๊ฐ€ ํด์ˆ˜๋ก ๋นจ๋ฆฌ ๋๋‚˜๊ฒŒ ๋งŒ๋“ค์–ด์„œ, ํ˜ธ์ถœํ•œ ์ˆœ์„œ์™€ ์™„๋ฃŒ๋˜๋Š” ์ˆœ์„œ๊ฐ€ ๋’ค์„ž์ด๋„๋ก ํ–ˆ๋‹ค.

const ids = [1, 2, 3, 4, 5];
const fakeApi = (id) =>
  new Promise((res) => setTimeout(() => res(id), (6 - id) * 60));

1. forEach โ€” async๋ฅผ ๊ธฐ๋‹ค๋ ค์ฃผ์ง€ ์•Š๋Š”๋‹ค

๋‚ด๊ฐ€ ์ฒ˜์Œ ์ง  ์ฝ”๋“œ๊ฐ€ ์ด๊ฑฐ์˜€๋‹ค. (git ๊ธฐ๋ก์„ ๋ณด๋‹ˆ ์ง„์งœ๋กœ ์ด๋žฌ๋‹ค ๐Ÿ˜…)

console.time("forEach");
const results = [];
ids.forEach(async (id) => {
  const r = await fakeApi(id);
  results.push(r);
});
console.timeEnd("forEach");
console.log("forEach ์งํ›„:", results);

์‹ค์ œ ์ถœ๋ ฅ:

forEach: 0.21ms
forEach ์งํ›„: []
(500ms ๋’ค): [ 5, 4, 3, 2, 1 ]

console.timeEnd๊ฐ€ 0.21ms ๋งŒ์— ์ฐํžŒ๋‹ค. forEach๋Š” ์ฝœ๋ฐฑ์ด ๋Œ๋ ค์ค€ Promise๋ฅผ ๊ฑฐ๋“ค๋– ๋ณด์ง€๋„ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ฝœ๋ฐฑ์„ ์ฃฝ ์‹คํ–‰๋งŒ ํ•˜๊ณ  ๋ฐ”๋กœ ๋‹ค์Œ ์ค„๋กœ ๋„˜์–ด๊ฐ„๋‹ค. ๊ทธ๋ž˜์„œ forEach ์งํ›„์—” results๊ฐ€ ํ…… ๋น„์–ด ์žˆ๊ณ , ํ•œ์ฐธ ๋’ค์—์•ผ ์™„๋ฃŒ๋œ ์ˆœ์„œ(5,4,3,2,1)๋Œ€๋กœ ์ฑ„์›Œ์ง„๋‹ค.

๋‚ด ํ”„๋กœ์ ํŠธ์—์„œ ๋ฌด์Šจ ์ผ์ด ๋ฒŒ์–ด์กŒ๋ƒ๋ฉด โ€” ๊ฒ€์ƒ‰์ด ๋๋‚˜๊ธฐ๋„ ์ „์— "๋‹ค์Œ ๋‹จ๊ณ„"๊ฐ€ ์‹คํ–‰๋ผ์„œ, ์•„์ง ์•ˆ ๋งŒ๋“ค์–ด์ง„ ์žฌ์ƒ๋ชฉ๋ก์— ๊ณก์„ ๋„ฃ์œผ๋ ค๋‹ค ๊นจ์กŒ๋‹ค. forEach๊ฐ€ await๋ฅผ ๊ธฐ๋‹ค๋ฆฐ๋‹ค๊ณ  ์ฐฉ๊ฐํ•œ ๊ฒŒ ์›์ธ์ด์—ˆ๋‹ค.

MDN๋„ ๋ช…์‹œํ•œ๋‹ค: forEach๋Š” promise๋ฅผ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๋Š”๋‹ค("it does not wait for promises").

2. for...of โ€” ์ˆœ์ฐจ์ ์ด๊ณ , ํ™•์‹คํ•˜๊ณ , ๋А๋ฆฌ๋‹ค

for...of๋Š” ๋ฃจํ”„ ์•ˆ์˜ await์—์„œ ์ง„์งœ๋กœ ๋ฉˆ์ถ˜๋‹ค. ํ•˜๋‚˜ ๋๋‚˜์•ผ ๋‹ค์Œ์œผ๋กœ ๊ฐ„๋‹ค.

console.time("for...of");
const seq = [];
for (const id of ids) {
  seq.push(await fakeApi(id));
}
console.timeEnd("for...of");
for...of: 904.705ms
๊ฒฐ๊ณผ: [ 1, 2, 3, 4, 5 ]

์ˆœ์„œ๋„ ์™„๋ฒฝํ•˜๊ณ  ๋™์ž‘๋„ ํ™•์‹คํ•˜๋‹ค. ๋Œ€์‹  ๋А๋ฆฌ๋‹ค. ๊ฐ ํ˜ธ์ถœ ์‹œ๊ฐ„์ด ๊ทธ๋Œ€๋กœ ํ•ฉ์‚ฐ๋œ๋‹ค(60+120+โ€ฆ+300 โ‰ˆ 900ms). ๊ณก์ด ๋งŽ์œผ๋ฉด ๋‹ต๋‹ตํ•  ๋งŒํผ ๋А๋ ค์ง„๋‹ค.

3. map + Promise.all โ€” ๋™์‹œ์— ์˜๊ธฐ

map์€ forEach์™€ ๋‹ฌ๋ฆฌ ์ฝœ๋ฐฑ์˜ ๋ฐ˜ํ™˜๊ฐ’์œผ๋กœ ์ƒˆ ๋ฐฐ์—ด์„ ๋งŒ๋“ ๋‹ค. async ์ฝœ๋ฐฑ์„ ๋„ฃ์œผ๋ฉด Promise๋“ค์˜ ๋ฐฐ์—ด์ด ๋‚˜์˜จ๋‹ค. ์ด๊ฑธ Promise.all๋กœ ํ•œ ๋ฒˆ์— ๊ธฐ๋‹ค๋ฆฐ๋‹ค.

console.time("Promise.all");
const done = [];
const promises = ids.map(async (id) => {
  const r = await fakeApi(id);
  done.push(r);        // ์™„๋ฃŒ๋˜๋Š” ์ˆœ์„œ๋Œ€๋กœ ์Œ“์ธ๋‹ค
  return r;
});
const all = await Promise.all(promises);
console.timeEnd("Promise.all");
console.log("์™„๋ฃŒ๋œ ์ˆœ์„œ:", done);
console.log("๊ฒฐ๊ณผ ๋ฐฐ์—ด:", all);
Promise.all: 301.521ms
์™„๋ฃŒ๋œ ์ˆœ์„œ: [ 5, 4, 3, 2, 1 ]
๊ฒฐ๊ณผ ๋ฐฐ์—ด:  [ 1, 2, 3, 4, 5 ]

for...of๊ฐ€ 904ms์ธ๋ฐ ์ด๊ฑด 301ms. ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ๋‹ค ์ด๋ฒ„๋ฆฐ ๋’ค ํ•œ๊บผ๋ฒˆ์— ๋ชจ์œผ๋‹ˆ, ์ „์ฒด ์‹œ๊ฐ„์ด "๊ฐ€์žฅ ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ํ•˜๋‚˜(300ms)"์— ์ˆ˜๋ ดํ•œ๋‹ค.

๊ทธ๋•Œ ๋‚ด๊ฐ€ ํ‹€๋ ธ๋˜ ๊ฒƒ: "Promise.all์ด ์ˆœ์„œ๋ฅผ ๋ณด์žฅํ•œ๋‹ค"

์˜ˆ์ „ ๊ธ€์—์„œ ๋‚˜๋Š” "Promise.all์ด ์ˆœ์„œ๋ฅผ ๋ณด์žฅํ•œ๋‹ค"๊ณ  ๋ญ‰๋šฑ๊ทธ๋ ค ์ผ๋‹ค. ์ ˆ๋ฐ˜๋งŒ ๋งž๋Š” ๋ง์ด์—ˆ๋‹ค. ์œ„ ์ถœ๋ ฅ์ด ์ •ํ™•ํžˆ ๋ณด์—ฌ์ค€๋‹ค.

  • ์™„๋ฃŒ๋˜๋Š” ์ˆœ์„œ๋Š” ๋ณด์žฅ๋˜์ง€ ์•Š๋Š”๋‹ค โ†’ done = [5, 4, 3, 2, 1] (๋นจ๋ฆฌ ๋๋‚œ ๊ฒŒ ๋จผ์ € ์Œ“์ธ๋‹ค)
  • Promise.all์ด ๋Œ๋ ค์ฃผ๋Š” ๊ฒฐ๊ณผ ๋ฐฐ์—ด์˜ ์ˆœ์„œ๋Š” ์ž…๋ ฅ ์ˆœ์„œ ๊ทธ๋Œ€๋กœ๋‹ค โ†’ all = [1, 2, 3, 4, 5]

์ฆ‰ Promise.all์€ ์‹คํ–‰์„ ์ˆœ์„œ๋Œ€๋กœ ํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ๊ฒฐ๊ณผ๋ฅผ ์ž…๋ ฅ ์ˆœ์„œ์— ๋งž์ถฐ ์ •๋ ฌํ•ด์„œ ๋Œ๋ ค์ค„ ๋ฟ์ด๋‹ค. ์‹คํ–‰ ์ž์ฒด๋Š” ๋™์‹œ์— ํฉ์–ด์ง„๋‹ค. ์˜ˆ์ „์˜ ๋‚˜๋Š” ์ด ๋‘˜์„ ํ—ท๊ฐˆ๋ ธ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ํ•˜๋‚˜ ๋”. ํ”ํžˆ "๋ณ‘๋ ฌ(parallel)"์ด๋ผ๊ณ  ํ•˜์ง€๋งŒ, JavaScript๋Š” ์‹ฑ๊ธ€ ์Šค๋ ˆ๋“œ๋‹ค. CPU๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ๋กœ ๋„๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ๋„คํŠธ์›Œํฌ I/O๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์‹œ๊ฐ„์ด ๊ฒน์น˜๋Š” ๊ฒƒ๋ฟ์ด๋ผ ์ •ํ™•ํžˆ๋Š” "๋™์‹œ(concurrent)"์— ๊ฐ€๊น๋‹ค.

์ง„์งœ ๊ตํ›ˆ: ๋น ๋ฅธ ๊ฒŒ ํ•ญ์ƒ ์ •๋‹ต์€ ์•„๋‹ˆ๋‹ค

์—ฌ๊ธฐ๊นŒ์ง€๋งŒ ๋ณด๋ฉด "๊ทธ๋Ÿผ ๋ฌด์กฐ๊ฑด map + Promise.all์ด ์ตœ๊ณ ๋„ค?" ์‹ถ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๋‚ด ํ”„๋กœ์ ํŠธ์˜ ์ตœ์ข… ์ฝ”๋“œ๋Š” ์ด๋ ‡๊ฒŒ ๋‚˜๋‰˜์–ด ์žˆ๋‹ค.

  • ๊ณก ๊ฒ€์ƒ‰(์ฝ๊ธฐ) โ†’ map + Promise.all (๋™์‹œ, ๋น ๋ฆ„)
  • ์žฌ์ƒ๋ชฉ๋ก์— ๊ณก ์ถ”๊ฐ€(์“ฐ๊ธฐ) โ†’ for ๋ฃจํ”„ + await (์ˆœ์ฐจ)
// ๊ฒ€์ƒ‰: ๋™์‹œ์—
const result = await Promise.all(checkValue.map((song) => searchVideo(...)));

// ์ถ”๊ฐ€: ํ•˜๋‚˜์”ฉ ์ˆœ์ฐจ์ ์œผ๋กœ
for (let i = 0; i < songArr.length; i++) {
  await addToPlayList(accessToken, playlistId, songArr[i].videoId);
  setProgress({ done: i + 1, total: songArr.length });
}

์™œ ์ถ”๊ฐ€๋Š” ์ˆœ์ฐจ๋กœ ํ–ˆ๋ƒ๋ฉด โ€” ์žฌ์ƒ๋ชฉ๋ก ์ถ”๊ฐ€๋Š” ๊ฐ™์€ ์žฌ์ƒ๋ชฉ๋ก์„ ์—ฌ๋Ÿฌ ์š”์ฒญ์ด ๋™์‹œ์— ์ˆ˜์ •ํ•˜๋Š” ์“ฐ๊ธฐ ์ž‘์—…์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. YouTube์˜ playlistItems.insert๋ฅผ ๋™์‹œ์— ์—ฌ๋Ÿฌ ๊ฐœ ์˜๋ฉด ๊ฐ™์€ ๋ฆฌ์†Œ์Šค๋ฅผ ๋™์‹œ์— ๊ณ ์น˜๋ ค๋‹ค 409 CONFLICT๋กœ ์‹คํŒจํ•˜๊ธฐ ์‰ฝ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๋ฌด์—‡๋ณด๋‹ค ์žฌ์ƒ๋ชฉ๋ก์€ ๋„ฃ๋Š” ์ˆœ์„œ๊ฐ€ ๊ฒฐ๊ณผ๋ฌผ์ด๋‹ค โ€” ๋™์‹œ์— ์˜๋ฉด ์™„๋ฃŒ ์ˆœ์„œ๊ฐ€ ๋’ค์„ž์—ฌ์„œ ๊ณก ์ˆœ์„œ๋ฅผ ๋ณด์žฅํ•  ์ˆ˜ ์—†๋‹ค. ๊ทธ๋ž˜์„œ ์‹ค์ œ ์ฝ”๋“œ๋„ for ๋ฃจํ”„ ์•ˆ์—์„œ ํ•˜๋‚˜์”ฉ awaitํ•˜๋ฉฐ, ํ•œ ๊ณก ๋๋‚  ๋•Œ๋งˆ๋‹ค ์ง„ํ–‰๋ฅ ์„ ๊ฐฑ์‹ ํ•˜๋Š” ๊ตฌ์กฐ๋‹ค.

๊ทธ๋ž˜์„œ ๊ฒฐ๋ก ์€ ์ด๋ ‡๊ฒŒ ์ •๋ฆฌ๋๋‹ค.

์ƒํ™ฉ ์„ ํƒ ์ด์œ 
์ˆœ์„œยท์„ฑ๊ณต์ด ์ค‘์š”, ์†Œ๋Ÿ‰ for...of + await ํ™•์‹คํ•จ
๋‹ค์ˆ˜์˜ ์ฝ๊ธฐ, ์†๋„ ์ค‘์š” map + Promise.all ๋™์‹œ ์ฒ˜๋ฆฌ๋กœ ๋น ๋ฆ„
์“ฐ๊ธฐ๋ฅผ ๋Œ€๋Ÿ‰์œผ๋กœ ์ˆœ์ฐจ or ๋™์‹œ์„ฑ ์ œํ•œ ๋™์‹œ ์ˆ˜์ • ์ถฉ๋Œยท์ˆœ์„œ ๋ณด์žฅ
async๋ฅผ ์ˆœํšŒ forEach๋Š” ์“ฐ์ง€ ๋ง ๊ฒƒ await๋ฅผ ์•ˆ ๊ธฐ๋‹ค๋ฆผ

๋ค์œผ๋กœ ์•Œ์•„๋‘˜ ๊ฒƒ: Promise.all์€ ํ•˜๋‚˜๋งŒ ์‹คํŒจํ•ด๋„ ์ „์ฒด๊ฐ€ ์ฆ‰์‹œ ์‹คํŒจํ•œ๋‹ค (fail-fast). "์ผ๋ถ€ ์‹คํŒจ๋Š” ๋„˜์–ด๊ฐ€๊ณ  ์‹ถ๋‹ค"๋ฉด Promise.allSettled, "๋™์‹œ์— ๋„ˆ๋ฌด ๋งŽ์ด ์˜๋Š” ๊ฒŒ ๋ฌธ์ œ"๋ผ๋ฉด ํ•œ ๋ฒˆ์— N๊ฐœ์”ฉ๋งŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๋™์‹œ์„ฑ ์ œํ•œ์„ ์“ฐ๋ฉด ๋œ๋‹ค.

์ •๋ฆฌ

  • forEach + async๋Š” await๋ฅผ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๋Š”๋‹ค. ์ˆœํšŒ ๋น„๋™๊ธฐ์—” ์“ฐ์ง€ ๋ง์ž.
  • for...of + await๋Š” ์ˆœ์ฐจ์ ยทํ™•์‹คํ•˜์ง€๋งŒ ๋А๋ฆฌ๋‹ค.
  • map + Promise.all์€ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•ด ๋น ๋ฅด๊ณ , ๊ฒฐ๊ณผ ๋ฐฐ์—ด์˜ ์ˆœ์„œ๋Š” ์ž…๋ ฅ์ˆœ์œผ๋กœ ๋ณด์žฅํ•œ๋‹ค. (๋‹จ, ์‹คํ–‰/์™„๋ฃŒ ์ˆœ์„œ๋Š” ์•„๋‹ˆ๋‹ค.)
  • ๊ทธ๋ฆฌ๊ณ  ์ œ์ผ ์ค‘์š”ํ•œ ๊ฒƒ โ€” ์ฝ๊ธฐ๋Š” ๋™์‹œ์—, ์“ฐ๊ธฐ๋Š” ์กฐ์‹ฌ์Šค๋Ÿฝ๊ฒŒ. ๋น ๋ฅธ ๊ฒŒ ๋Š˜ ์˜ณ์€ ๊ฑด ์•„๋‹ˆ์—ˆ๋‹ค.

์˜ˆ์ „์˜ ๋‚˜๋Š” "map์ด ์ œ์ผ ๋น ๋ฅด๋‹ˆ ์ตœ๊ณ "๋ผ๊ณ  ๊ฒฐ๋ก  ๋ƒˆ์ง€๋งŒ, ์‹ค์ œ๋กœ ์„œ๋น„์Šค๋ฅผ ๊ตด๋ ค๋ณด๋‹ˆ ๋‹ต์€ ์ƒํ™ฉ๋งˆ๋‹ค ๋‹ฌ๋ž๋‹ค. ํ‹€๋ ธ๋˜ ๊ฑธ ๋‹ค์‹œ ์งš์–ด๋ณด๋Š” ๊ฒƒ๋„ ์„ฑ์žฅ์ด๋ผ ๋ฏฟ๋Š”๋‹ค.

โ† ๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ
ยฉ 2026 ยท Groot's Tech Blog. Built with Gatsby ๐ŸŒฑ