๋น„๋™๊ธฐ์™€ ํ”„๋กœ๋ฏธ์Šค

@choi2021 ยท November 27, 2022 ยท 18 min read

๐Ÿ’ก ๋น„๋™๊ธฐ์™€ ํ”„๋กœ๋ฏธ์Šค

"๋™๊ธฐ๋‹ค, ๋น„๋™๊ธฐ๋‹ค", ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ๋งŽ์ด ๋“ค์–ด์˜จ ๋ง์ด๋‹ค. ๋น„๋™๊ธฐ์™€ ํ”„๋กœ๋ฏธ์Šค๋ฅผ ์ดํ•ดํ•œ๋‹ค๋Š” ๊ฒƒ์€ ํ”„๋กœ๊ทธ๋žจ์˜ ํ๋ฆ„์„ ์ดํ•ดํ•˜๊ณ  ์„ฑ๋Šฅ ์ตœ์ ํ™”, ์—๋Ÿฌ ํ•ธ๋“ค๋ง ๋“ฑ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ์•„์ฃผ ์ค‘์š”ํ•œ ์š”์†Œ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ •๋ฆฌํ•ด ๋ณด๊ณ ์ž ํ•œ๋‹ค.

๋จผ์ € ๋™๊ธฐ์™€ ๋น„๋™๊ธฐ๋ž€ ๋ญ˜๊นŒ?

๐Ÿ“‚๋™๊ธฐ์™€ ๋น„๋™๊ธฐ

์œ„ ๊ทธ๋ฆผ์˜ ๋™๊ธฐ์ ์ธ ๊ณผ์ •์€ ํ•˜๋‚˜์˜ ์ผ์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ ธ๋‹ค๊ฐ€ ๋‹ค์Œ ์ผ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•˜๊ณ , ์•„๋ž˜ ๊ทธ๋ฆผ์€ ๋น„๋™๊ธฐ ๊ณผ์ •์œผ๋กœ ํ•˜๋‚˜์˜ ์ผ์„ ๋๋‚  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์ผ์„ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•˜๊ณ  ์žˆ๋‹ค. ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ๊ณผ์ •์€ ์—ฌ๋Ÿฌ ์ผ์„ ๊ฐ™์ด ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ํ›จ์”ฌ ์‹œ๊ฐ„์ด ์ ๊ฒŒ ๊ฑธ๋ฆฌ๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•˜๋‚˜์˜ ์ผ์„ ์ฒ˜๋ฆฌํ•˜๋Š”๋ฐ ์˜ค๋žœ ์‹œ๊ฐ„์ด ๊ฑธ๋ ค ์ดํ›„ ์ž‘์—…์„ ๋ง‰๊ฒŒ ๋˜๋Š” ๊ฒฝ์šฐ, ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ํ†ตํ•ด ํšจ์œจ์„ ๋†’์ผ ์ˆ˜ ์žˆ๋‹ค.

sync
sync
async
async

a,b,c ๋ผ๋Š” ๋ณ€์ˆ˜๋ฅผ ๊ฐ„๋‹จํ•˜๊ฒŒ ํ˜ธ์ถœํ•˜๊ณ  ์žˆ๋Š” ์˜ˆ์‹œ๋ฅผ ๋ณด์ž.

const a = 1
const b = 2
const c = 3

console.log(a)
console.log(b)
console.log(c)

//๊ฒฐ๊ณผ: 1,2,3

//๋น„๋™๊ธฐ
console.log(a)
setTimeout(() => {
  console.log(b)
}, 0)
console.log(c)

//๊ฒฐ๊ณผ: 1,3,2

๋™๊ธฐ ์ฝ”๋“œ์ธ ์œ„์˜ ์˜ˆ์‹œ๋Š” ๊ฒฐ๊ณผ๊ฐ€ ์ผ์˜ ์ˆœ์„œ๋Œ€๋กœ 1,2,3์ด๋ž€ ๊ฒฐ๊ณผ๋ฅผ ๋‚˜ํƒ€๋‚ด๊ณ , ๋น„๋™๊ธฐ ์ฝ”๋“œ์ธ ์•„๋ž˜์˜ ์˜ˆ์‹œ๋Š” 1,3,2๋ผ๋Š” ์ฝ”๋“œ๋ฅผ ์ ์€ ์ˆœ์„œ์™€ ๋‹ค๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ–๊ฒŒ ๋˜์—ˆ๋‹ค.

console.log(b)๋ฅผ console.log(c)๋ณด๋‹ค ๋จผ์ € ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์—”์ง„์ด ์ฝ์—ˆ์ง€๋งŒ, ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ์— ์˜ํ•ด ์ˆœ์„œ๋Œ€๋กœ ์ง„ํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ b๋ฅผ ๋‹ค๋ฅธ ๊ณณ์—์„œ ์ฒ˜๋ฆฌํ•œ ํ›„์— ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค. ๋งŒ์•ฝ b๊ฐ€ ์ฒ˜๋ฆฌํ•˜๋Š”๋ฐ ์—„์ฒญ ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ๊ณผ์ •์ด๋ผ๊ณ  ๊ฐ€์ •ํ•œ๋‹ค๋ฉด, ๋™๊ธฐ์  ์ฝ”๋“œ์—์„œ๋Š” ์˜ค๋žœ ์‹œ๊ฐ„ ๋’ค์— c๊ฐ€ ํ˜ธ์ถœ์ด ๊ฐ€๋Šฅํ•˜๋‹ค. ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋กœ ์ธํ•ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” b๋ผ๋Š” ์ผ์„ ๋„˜๊ฒจ์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ๋™์•ˆ c๋ฅผ ์ฒ˜๋ฆฌํ•˜๋ฉด ํ›จ์”ฌ ํšจ์œจ์ ์œผ๋กœ ์ผ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋Ÿฌ๋ฉด ์ด๋ ‡๊ฒŒ ๋ณด๋‚ด์ง„ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋Š” ์–ด๋””์„œ, ์–ด๋–ป๊ฒŒ ์ด๋ฃจ์–ด์ง€๋Š” ๊ฑธ๊นŒ?

โฐ ๋ชจ๋“  ์ผ์˜ ์ˆœ์„œ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์Šค์ผ€์ค„๋Ÿฌ, Event loop

์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋Š” ์‹ฑ๊ธ€ ์Šค๋ ˆ๋“œ๋กœ ์ผ์ด ์ฒ˜๋ฆฌ๋œ๋‹ค. ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ผ์„ ํ•œ๋ฒˆ์— ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ํ•œ๋ฒˆ์˜ ํ•˜๋‚˜์˜ ์ผ๋งŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์˜๋ฏธ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์•ž์„œ ๋ณด์•˜๋˜ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋Š” ์–ด๋””์„œ ์ด๋ฃจ์–ด์ง€๋Š” ์˜๋ฌธ์ด ๋“ ๋‹ค. ๋น„๋™๊ธฐ๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ผ์„ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด๊ณ , ์šฐ๋ฆฌ๊ฐ€ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์ด์šฉํ•  ๋•Œ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ผ์„ ๋™์‹œ์— ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋А๊ปด์ง€๋Š”๋ฐ ์–ด๋–ป๊ฒŒ ๋œ ๊ฑธ๊นŒ?

์ด๊ฒƒ์„ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋จผ์ € ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์—”์ง„๊ณผ ๋ธŒ๋ผ์šฐ์ €์˜ ์—ญํ• ์„ ์ดํ•ดํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค.

๋จผ์ € ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์—”์ง„์—๋Š” ํž™๊ณผ ์ฝœ์Šคํƒ์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋‹ค. ์ฝœ์Šคํƒ์€ ์‹คํ–‰ ์ปจํ…์ŠคํŠธ ์Šคํƒ๊ณผ ๊ฐ™์€ ๊ฒƒ์œผ๋กœ, ์‹คํ–‰ ์ปจํ…์ŠคํŠธ์˜ ์ˆœ์„œ๋ฅผ ๊ธฐ์–ตํ•˜๊ณ  ์žˆ๋‹ค. ํž™์—๋Š” ๊ฐ์ฒด๊ฐ€ ์ €์žฅ๋˜๋Š” ๋ฉ”๋ชจ๋ฆฌ ๊ณต๊ฐ„์œผ๋กœ ๋™์ ์œผ๋กœ ์ƒ๊ธฐ๋Š” ๋ณ€์ˆ˜๋“ค, ํฌ๊ธฐ๋ฅผ ์ •ํ•  ์ˆ˜ ์—†๋Š” ๊ฐ์ฒด๋“ค์„ ์ €์žฅํ•˜๊ณ  ์‹คํ–‰ ์ปจํ…์ŠคํŠธ๋Š” ํž™์— ์ €์žฅ๋œ ๊ฐ์ฒด๋“ค์„ ์ฐธ์กฐํ•˜๊ณ  ์žˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์•ž์„œ ๋งํ•œ ๋Œ€๋กœ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ž์ฒด๋Š” ์‹ฑ๊ธ€ ์Šค๋ ˆ๋“œ๋กœ ์ฝœ์Šคํƒ์— ์Œ“์—ฌ์žˆ๋Š” ์ˆœ์„œ๋กœ ์ฝ”๋“œ๋ฅผ ํ‰๊ฐ€ํ•˜๊ณ  ์‹คํ–‰ํ•˜๋Š” ๊ธฐ๋Šฅ๋งŒ ๊ฐ–๊ณ  ์žˆ๋‹ค.

๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด์„œ๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์—”์ง„์ด ์ž‘๋™ํ•˜๋Š” Node Js๋‚˜ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋„์™€์ค˜์•ผ ํ•œ๋‹ค. ์•ž์„  ์ฝ”๋“œ์—์„œ ์‚ฌ์šฉ๋œ setTimeout๊ณผ ๊ฐ™์€ ๋ธŒ๋ผ์šฐ์ €/Node API๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์—”์ง„์€ ํ‰๊ฐ€ํ•˜๊ณ  ์‹คํ–‰ํ•˜๋Š”๋ฐ, ํ˜ธ์ถœ๋˜๋Š” ์‹œ์ ๊ณผ ์ฝœ๋ฐฑํ•จ์ˆ˜์˜ ๋“ฑ๋ก์€ ๋ธŒ๋ผ์šฐ์ €์™€ Node.js๊ฐ€ ํ•ด ์ค€๋‹ค. ์ด๋ ‡๊ฒŒ ๋“ฑ๋กํ•  ์ฝœ๋ฐฑํ•จ์ˆ˜๋ฅผ ๋ณด๊ด€ํ•˜๋Š” ๊ณณ์€ Task Queue๋‹ค. Task Queue์— ์ €์žฅ๋œ callbackํ•จ์ˆ˜๋Š” ํ•˜๋‚˜์”ฉ event loop์ด callstack์ด ๋น„์–ด ์žˆ์„ ๋•Œ ๊ฐ€์ ธ์™€ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์—”์ง„์ด ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค.

์ด๋ ‡๊ฒŒ ๋‹ค๋ฅธ ์—ญํ• ์„ ๊ฐ–๊ณ  ์žˆ๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์—”์ง„๊ณผ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์—ฐ๊ฒฐํ•ด ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์˜ ๋™์‹œ์„ฑ์„ ์ง€์›ํ•˜๋Š” ๊ฒƒ์ด Event loop์ด๋‹ค.

eventloop

์•ž์„  ๋น„๋™๊ธฐ ์˜ˆ์ œ๋ฅผ ๋‹ค์‹œ ์„ค๋ช…ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

const a = 1
const b = 2
const c = 3

console.log(a)
setTimeout(() => {
  console.log(b)
}, 0)
console.log(c)

//๊ฒฐ๊ณผ: 1,3,2
  1. ์ „์—ญ์ปจํ…์ŠคํŠธ๊ฐ€ ๋งŒ๋“ค์–ด์ง€๊ณ  ์ฝœ์Šคํƒ์— ๋“ค์–ด๊ฐ„๋‹ค.
  2. ํ‰๊ฐ€ ๊ณผ์ •์„ ํ†ตํ•ด ๋ณ€์ˆ˜ a,b,c๊ฐ€ ๋“ฑ๋ก๋˜๊ณ  ์‹คํ–‰ ๊ณผ์ •์—์„œ WebAPI์ธ SetTimeOut์€ ํ•จ์ˆ˜ ์ปจํ…์ŠคํŠธ๋ฅผ ๋งŒ๋“ค์–ด ์ฝœ์Šคํƒ์— ์ถ”๊ฐ€๋œ๋‹ค.
  3. SetTimeOut ๋‚ด๋ถ€ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋Š” ๋ธŒ๋ผ์šฐ์ €๋กœ ๋„˜์–ด๊ฐ€๊ณ  ํ•จ์ˆ˜ ์ปจํ…์ŠคํŠธ๋Š” ์ข…๋ฃŒ๋˜์–ด ์ฝœ์Šคํƒ์—์„œ ์ œ๊ฑฐ๋œ๋‹ค.
  4. ํƒ€์ด๋จธ๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด taskQueue์— ์ฝœ๋ฐฑ ํ•จ์ˆ˜๊ฐ€ ๋“ฑ๋ก๋˜๋Š”๋ฐ, ์ด ๊ณผ์ •์—์„œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์—”์ง„์€ ๋‹ค์Œ ์‹คํ–‰ ์ฝ”๋“œ์ธ console.log(c)๊ฐ€ ์ฒ˜๋ฆฌํ•œ๋‹ค.
  5. call stack์ด ๋‹ค ๋น„๋ฉด taskQueue์— ์žˆ๋˜ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ๊ฐ€์ ธ์™€ console.log(c)๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค.

์ด๋Ÿฌํ•œ ๊ณผ์ • ๋•Œ๋ฌธ์— ๊ฒฐ๊ณผ๊ฐ€ 1,3,2๋กœ ์ฒ˜๋ฆฌ๋˜์—ˆ๋˜ ๊ฒƒ์ด๋‹ค.

์ฃผ์˜ํ•  ์ ์€ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์—”์ง„์€ ์‹ฑ๊ธ€ ์Šค๋ ˆ๋“œ์ด์ง€๋งŒ ๋ธŒ๋ผ์šฐ์ €๋Š” ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ๋กœ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์ผ์„ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•ด ์ค„ ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋Ÿฌ๋ฉด ๋ชจ๋“  ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ์ฝœ๋ฐฑํ•จ์ˆ˜๋กœ ํ•˜๋ฉด ๋˜๋Š”๊ฑธ๊นŒ?

โ— ์ฝœ๋ฐฑ ํ•จ์ˆ˜์˜ ํ•œ๊ณ„

์ฝœ๋ฐฑํ•จ์ˆ˜๋Š” ์ฝœ๋ฐฑํ•จ์ˆ˜ ๋‚ด๋ถ€ ๊ฒฐ๊ณผ๋ฅผ ์™ธ๋ถ€๋กœ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์—†๋‹ค. ์•ž์„œ ์‚ฌ์šฉํ•œ setTimeOut์„ ์˜ˆ๋กœ ์„ค๋ช…ํ•ด๋ณด๋ฉด ์ฝœ๋ฐฑ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜๋Š” ์‹œ์ ์—๋Š” setTimeOut์˜ ํ•จ์ˆ˜ ์ปจํ…์ŠคํŠธ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์ƒ์œ„ ์Šค์ฝ”ํ”„์˜ ๋ณ€์ˆ˜์— ๊ฐ’์„ ํ• ๋‹นํ•˜๊ฑฐ๋‚˜ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— try-catch๋ฌธ์œผ๋กœ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋ คํ•ด๋„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์‹œ์ ์—์„œ์˜ ์—๋Ÿฌ๋Š” ์žกํžˆ์ง€ ์•Š๋Š”๋‹ค.

์‹คํ–‰ ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ ๋‹ค์Œ ๊ณผ์ •์„ ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ฝœ๋ฐฑ ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ ์ฒ˜๋ฆฌ๋˜์–ด์•ผ ํ•˜๊ณ , ์ด ๊ณผ์ •์ด ์ค‘์ฒฉ๋˜๊ฒŒ ๋˜๋ฉด์„œ ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์ง€๋Š” "Callback hell"์ด ๋œ๋‹ค.

์•„๋ž˜ ์ฝ”๋“œ๋Š” Callback hell์˜ ์˜ˆ์ œ๋กœ ๋กœ๊ทธ์ธ ๊ณผ์ •์„ ๊ตฌํ˜„ํ•œ ์ฝ”๋“œ๋‹ค. ์ด๋ ‡๊ฒŒ ์ฝ”๋“œ๊ฐ€ ์งœ์—ฌ์ ธ ์žˆ๋‹ค๋ฉด ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์ง€๊ณ , ์œ ์ง€ ๋ณด์ˆ˜์—๋„ ์–ด๋ ค์šด ๋‹จ์ ์„ ๊ฐ–๋Š”๋‹ค.

class UserStorage {
  loginUser(id, password, onSuccess, onError) {
    setTimeout(() => {
      if (
        (id === "seul" && password === "123") ||
        (id === "kim" && password === "456")
      ) {
        onSuccess(id)
      } else {
        onError(new Error("error"))
      }
    }, 2000)
  }

  getRoles(user, onSuccess, onError) {
    setTimeout(() => {
      if (user === "seul") {
        onSuccess({ name: "seul", role: "admin" })
      } else {
        onError(new Error("error"))
      }
    }, 1000)
  }
}

const userStorage = new UserStorage()
const id = prompt("์•„์ด๋””๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”!")
const password = prompt("๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”!!")

userStorage.loginUser(
  id,
  password,
  user => {
    userStorage.getRoles(
      user,
      userWithRole => {
        alert(
          `hello ${userWithRole.name}, you have a ${userWithRole.role} role`
        )
      },
      error => {
        console.log("์—๋Ÿฌ2")
      }
    )
  },
  error => {
    console.log("์—๋Ÿฌ1")
  }
)

๊ทธ๋Ÿฌ๋ฉด callbackํ•จ์ˆ˜์˜ ํ•œ๊ณ„๋ฅผ ์–ด๋–ป๊ฒŒ ๊ทน๋ณตํ•  ์ˆ˜ ์žˆ์„๊นŒ?

๐ŸŽˆ callback ํ•จ์ˆ˜์˜ ํ•œ๊ณ„๋ฅผ ๊ทน๋ณตํ•˜๊ธฐ ์œ„ํ•œ, Promise

Promise๋Š” ๋ง ๊ทธ๋Œ€๋กœ ์•ฝ์†, ์ดํ›„์— ์•ฝ์†๋œ ๊ฒฐ๊ณผ๋ฅผ ์ฃผ๊ฒ ๋‹ค๋Š” ์˜๋ฏธ๋ฅผ ๊ฐ€์ง€๋ฉฐ, ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ์ƒํƒœ์™€ ๊ฒฐ๊ณผ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๊ฐ์ฒด๋‹ค.

promise์˜ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ์ƒํƒœ๋Š” pending (์ˆ˜ํ–‰๋˜์ง€ ์•Š์€ ์ƒํƒœ), fulfilled (์„ฑ๊ณต), rejected (์‹คํŒจ), ์„ธ ๊ฐ€์ง€๊ฐ€ ์žˆ๊ณ , ์ƒํƒœ ์ •๋ณด๋Š” promise๋ฅผ ๋งŒ๋“ค ๋•Œ ์ „๋‹ฌ ๋ฐ›์€ ์ธ์ž์ธ resolve์™€ reject๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด์„œ ๋ณ€๊ฒฝ๋œ๋‹ค. resolve๋Š” ์„ฑ๊ณต ์‹œ์— ์ˆ˜ํ–‰ํ•  ํ•จ์ˆ˜, reject๋Š” ์‹คํŒจ ์‹œ์— ์ˆ˜ํ–‰ํ•  ํ•จ์ˆ˜์ด๋‹ค.

๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ ๋‹ค์Œ ํ›„์† ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด์„œ then, catch, finally ๋‚ด์žฅ ๋ฉ”์†Œ๋“œ๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, then์€ ์„ฑ๊ณตํ–ˆ์„ ๋•Œ๋ฅผ, catch๋Š” ์‹คํŒจํ–ˆ์„ ๋•Œ, finally๋Š” ์„ฑ๊ณต,์‹คํŒจ์™€ ์ƒ๊ด€์—†์ด ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ฐ๊ฐ์€ promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ฒด์ด๋‹์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

์ฒด์ด๋‹์—์„œ ์ฃผ์˜ํ•  ์ ์€ ์„ฑ๊ณต ์‹œ ๋ฐ˜ํ™˜ํ•œ ๊ฐ’์€ ๋‹ค์Œ then์˜ ์ธ์ž๋กœ ์ „๋‹ฌ ๋˜๊ณ , promise๋กœ ๊ณ„์†ํ•ด์„œ ์ฒ˜๋ฆฌ๊ฐ€ ๋œ๋‹ค๋Š” ์ ์ด๋‹ค. promise๋ฅผ ์ฒ˜์Œ ๋ฐฐ์šธ ๋•Œ ๊ฐ€์žฅ ์–ด๋ ค์šด ๋ถ€๋ถ„์ด์—ˆ๋‹ค. catch๋Š” ์–ด๋А ๊ณณ์—์„œ ์—๋Ÿฌ๊ฐ€ ๋‚˜๋„ ๋ฐ›์•„์˜ค๊ธฐ ๋•Œ๋ฌธ์— then์˜ ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ ์—๋Ÿฌ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ง€๋ง๊ณ , ํ•ญ์ƒ catch๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ํšจ์œจ์ ์ด๋‹ค.

function getData(state) {
  return new Promise(function (resolve, reject) {
    if (state === "์„ฑ๊ณต") {
      resolve("์„ฑ๊ณต")
    } else {
      reject(new Error("Request is failed"))
    }
  })
}

getData("์„ฑ๊ณต")
  .then(console.log) //์„ฑ๊ณต
  .catch(function (err) {
    console.log(err)
  })

getData("์‹คํŒจ")
  .then(console.log)
  .catch(function (err) {
    console.log(err) // Error: Request is failed
  })

๊ทธ๋Ÿฌ๋ฉด ์ด๋ฒˆ์—” ์•ž์„œ ๋ณด์•˜๋˜ callback hell ์˜ˆ์ œ๋ฅผ promise๋กœ ํ•ด๊ฒฐํ•ด๋ณด์ž

class UserStorage {
  loginUser(id, password) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (
          (id === "seul" && password === "123") ||
          (id === "kim" && password === "456")
        ) {
          resolve(id)
        } else {
          reject(new Error("์—๋Ÿฌ1"))
        }
      }, 2000)
    })
  }

  getRoles(user) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        if (user === "seul") {
          resolve({ name: "seul", role: "admin" })
        } else {
          reject(new Error("์—๋Ÿฌ2"))
        }
      }, 1000)
    })
  }
}

const userStorage = new UserStorage()
const id = prompt("์•„์ด๋””๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”!")
const password = prompt("๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”!!")

userStorage
  .loginUser(id, password)
  .then(userStorage.getRoles)
  .then(user => alert(`hello ${user.name}, you have a ${user.role} role`))
  .catch(console.log)

ํด๋ž˜์Šค ๋‚ด๋ถ€๋Š” ํฌ๊ฒŒ ๋‹ฌ๋ผ์ง„ ๊ฒƒ์€ ์—†์ง€๋งŒ, ์‚ฌ์šฉํ•  ๋•Œ ๋ณต์žก๋„๊ฐ€ ํฌ๊ฒŒ ์ค„์–ด ๊ฐ€๋…์„ฑ์ด ํ–ฅ์ƒ๋œ ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ”„๋กœ๋ฏธ์Šค๋ฅผ ํ†ตํ•ด callbackํ•จ์ˆ˜์˜ ํ•œ๊ณ„์ธ ํ›„์†์ฒ˜๋ฆฌ์™€ ์—๋Ÿฌ์ฒ˜๋ฆฌ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

Promise์˜ ๋ฉ”์†Œ๋“œ

promise์˜ ๋˜๋‹ค๋ฅธ ์žฅ์ ์€ promise ์ž์ฒด์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ๋‹ค์–‘ํ•œ ๋ฉ”์†Œ๋“œ๋‹ค.

1. Promise.all

์—ฌ๋Ÿฌ๊ฐœ์˜ promise๋ฅผ ๋™์‹œ์— ๋ณ‘๋ ฌ์ ์œผ๋กœ ์ด์šฉํ•  ๋•Œ, ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์†Œ๋“œ๋กœ, ์ธ์ž๋กœ ํ”„๋กœ๋ฏธ์Šค์˜ ์ดํ„ฐ๋Ÿฌ๋ธ”์„ ์ „๋‹ฌ๋ฐ›๋Š”๋‹ค. ์ „๋‹ฌ๋œ ์ดํ„ฐ๋Ÿฌ๋ธ”์˜ ์ˆœ์„œ๊ฐ€ ๋ณด์žฅ๋˜์–ด ๋ฐ˜ํ™˜๋˜๋ฉฐ, ์š”์†Œ์ค‘ ํ•˜๋‚˜๋ผ๋„ rejected์ƒํƒœ๊ฐ€ ๋˜๋ฉด ๋ฐ”๋กœ ์ข…๋ฃŒ๋˜๋ฉฐ ๊ฐ€์žฅ ๋จผ์ € rejected๋œ ๊ฒฐ๊ณผ๋ฅผ catch๋กœ ์ „๋‹ฌํ•œ๋‹ค.

์•„๋ž˜์˜ ์˜ˆ์ œ์—๋Š” ์„ธ๊ฐ€์ง€ ์„œ๋กœ ์˜์กด๋˜์ง€ ์•Š๋Š” promise๊ฐ€ ์ˆ˜ํ–‰๋˜๋Š”๋ฐ. chaining์„ ์ด์šฉํ•ด ์ด 6์ดˆ ์ •๋„๊ฐ€ ๊ฑธ๋ฆฌ๋Š” ์ƒํ™ฉ์ด๋‹ค.

const requestData1 = () =>
  new Promise(resolve => setTimeout(() => resolve(1), 3000))
const requestData2 = () =>
  new Promise(resolve => setTimeout(() => resolve(2), 2000))
const requestData3 = () =>
  new Promise(resolve => setTimeout(() => resolve(3), 1000))

const res = []
requestData1()
  .then(data => {
    //3์ดˆ๋’ค ๋ฐ›์•„์™€
    res.push(data)
    return requestData2()
  })
  .then(data => {
    //2์ดˆ๋’ค ๋ฐ›์•„์™€
    res.push(data)
    return requestData3()
  })
  .then(data => {
    //1์ดˆ๋’ค ๋ฐ›์•„์™€
    res.push(data)
    console.log(data) //์ด 6์ดˆ ๋’ค ํ˜ธ์ถœ
  })
  .catch(console.log)

promise.all์„ ์ด์šฉํ•˜๋ฉด ๊ฐ€์žฅ ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” requestData1์ด fulfilled ์ƒํƒœ๊ฐ€ ๋  ๋•Œ, ์ด 3์ดˆ ์ •๋„๊ฐ€ ์ง€๋‚˜๊ณ  then์œผ๋กœ ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ์ „๋‹ฌ๋˜์–ด ๋” ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

const requestData1 = () =>
  new Promise(resolve => setTimeout(() => resolve(1), 3000))
const requestData2 = () =>
  new Promise(resolve => setTimeout(() => resolve(2), 2000))
const requestData3 = () =>
  new Promise(resolve => setTimeout(() => resolve(3), 1000))

Promise.all([requestData1(), requestData2(), requestData3()])
  .then(console.log) //[1,2,3]
  .catch(console.error)

2. Promise.race

Promise.race๋Š” ๋ง ๊ทธ๋Œ€๋กœ ๊ฒฝ์ฃผํ•˜๋“ฏ์ด, ์ „๋‹ฌ ๋ฐ›์€ promise ์ค‘ ๊ฐ€์žฅ ๋จผ์ € fulfilled๋œ promise์˜ ์ฒ˜๋ฆฌ๊ฒฐ๊ณผ๋ฅผ resolveํ•˜๋Š” promise๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋Š” Promise.all๊ณผ ๋™์ผํ•˜๊ฒŒ ์š”์†Œ ์ค‘ ํ•˜๋‚˜๋ผ๋„ rejected์ƒํƒœ๊ฐ€ ๋˜๋ฉด ๋ฐ”๋กœ ์ข…๋ฃŒ๋˜๋ฉฐ ๊ฐ€์žฅ ๋จผ์ € rejected๋œ ๊ฒฐ๊ณผ๋ฅผ catch๋กœ ์ „๋‹ฌํ•œ๋‹ค.

const requestData1 = () =>
  new Promise(resolve => setTimeout(() => resolve(1), 3000))
const requestData2 = () =>
  new Promise(resolve => setTimeout(() => resolve(2), 2000))
const requestData3 = () =>
  new Promise(resolve => setTimeout(() => resolve(3), 1000))

Promise.race([requestData1(), requestData2(), requestData3()])
  .then(console.log) //3
  .catch(console.error)

3. Promise.allSettled

settled์€ fulfilled์ด๋‚˜ rejected๋กœ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๊ฐ€ ๋œ ์ƒํƒœ๋ฅผ ์˜๋ฏธํ•ด ์„ฑ๊ณต/์‹คํŒจ ์—ฌ๋ถ€์™€ ์ƒ๊ด€์—†์ด ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ด ์ฃผ๋Š” ๋ฉ”์†Œ๋“œ๋‹ค.

Promise.allSettled([
  new Promise((resolve) =>
    setTimeout(() => {
      resolve(1);
    }, 2000)
  ),
  new Promise((_, reject) =>
    setTimeout(() => {
      reject(new Error('error'));
    }, 1000)
  ),
]).then(console.log);

//๊ฒฐ๊ณผ:
[
  { status: 'fulfilled', value: 1 },
  {
    status: 'rejected',
    reason: Error: error
        at Timeout._onTimeout (C:\Users\juni2\projects\js study\DeepDive\main.js:32:14)
        at listOnTimeout (node:internal/timers:559:17)
        at processTimers (node:internal/timers:502:7)
  }
]

Promise ์ฒ˜๋ฆฌ๋Š” microtask Queue

event loop์„ ์„ค๋ช…ํ•˜๋ฉด์„œ callback์€ task queue์— ์ €์žฅ๋œ๋‹ค. promise๋Š” callbackํ•จ์ˆ˜์™€ ๋‹ฌ๋ฆฌ task Queue๊ฐ€ ์•„๋‹Œ micro-task Queue์— ์ €์žฅ๋œ๋‹ค.

promise

microtask queue๋Š” task queue๋ณด๋‹ค ์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’์•„ callstack์ด ๋น„๊ฒŒ ๋˜๋ฉด, event loop์ด microtask queue์˜ promise๋ฅผ ๋‹ค ๊ฐ€์ ธ์™€ ์ฒ˜๋ฆฌํ•œ๋‹ค. ์ด๋•Œ microtask Queue๊ฐ€ ๋น„์ง€ ์•Š์œผ๋ฉด ๋‹ค์Œ์œผ๋กœ event loop์ด ์ด๋™ํ•˜์ง€ ์•Š๊ณ  ๊ณ„์†ํ•ด์„œ ๋จธ๋ฌผ๋Ÿฌ์„œ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ฃฝ์–ด๋ฒ„๋ฆฌ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

function handleClick() {
  Promise.resolve(0).then(() => {
    handleClick() //์žฌ๊ท€๋กœ ๊ณ„์†ํ•ด์„œ promise๋ฅผ ์ถ”๊ฐ€ํ•ด
  })
}

handleClick()

๋งˆ์น˜๋ฉฐ

์ƒˆ๋กญ๊ฒŒ ์•Œ๊ฒŒ ๋œ ๊ฒƒ์€ promise์˜ then์—๊ฒŒ ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ๋‹ด์•„ ์ค„ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด์—ˆ๋‹ค. ํ•˜์ง€๋งŒ catch๋กœ ์—๋Ÿฌ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๋” ์ง๊ด€์ ์ด๊ณ  ํšจ์œจ์ ์ด๋ผ๋Š” ์ ๋„ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค. promise๋ฅผ ์—ฌ๋Ÿฌ ๊ฐœ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•œ ์ ์ด ์—†์–ด์„œ promise์˜ ๋ฉ”์†Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด๋ณธ ์ ์ด ์—†๋Š” ์•„์‰ฌ์›€์ด ์žˆ์—ˆ๋Š”๋ฐ, ํ•„์š”ํ•  ๋•Œ ๋– ์˜ฌ๋ฆด ์ˆ˜ ์žˆ๊ฒŒ ๊ณต๋ถ€ํ•œ ๊ฒƒ์ด ์ข‹์€ ๊ฒฝํ—˜์ด์—ˆ๋‹ค.

[์ฐธ์กฐ]

@choi2021
๋งค์ผ์˜ ์‹œํ–‰์ฐฉ์˜ค๋ฅผ ๊ธฐ๋กํ•˜๋Š” ๊ฐœ๋ฐœ์ผ์ง€์ž…๋‹ˆ๋‹ค.