ν”„λ‘œκ·Έλž˜λ°/JavaScript

[JavaScript] μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œ 비동기 처리 νŒ¨ν„΄μ— λŒ€ν•΄μ„œ μ•Œμ•„λ΄…μ‹œλ‹€. (Callback, Promise, Generator, async/await)

μš©λ‡½ 2024. 1. 8. 18:40
λ°˜μ‘ν˜•

[JavaScript] μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œ 비동기 처리 νŒ¨ν„΄μ— λŒ€ν•΄μ„œ μ•Œμ•„λ΄…μ‹œλ‹€. (Callback, Promise, Generator, async/await)

πŸ“– λ“€μ–΄κ°€λ©°

비동기 처리 νŒ¨ν„΄μ„ μ•Œμ•„λ³΄κΈ° μ•žμ„œ, 비동기 μ²˜λ¦¬λž€ λ¬΄μ—‡μΌκΉŒμš”?

비동기 μ²˜λ¦¬λž€?
νŠΉμ • μ½”λ“œμ˜ 연산이 μ™„λ£Œλ  λ•ŒκΉŒμ§€ 전체 μ½”λ“œμ˜ 싀행을 μ€‘λ‹¨ν•˜μ§€ μ•Šκ³ , λ‹€μŒ μ½”λ“œλ₯Ό μš°μ„ μ μœΌλ‘œ μ‹€ν–‰ν•˜λŠ” 방식을 λ§ν•©λ‹ˆλ‹€.
μ΄λ ‡κ²Œ λ™μž‘ν•˜λŠ” ν•¨μˆ˜λ₯Ό μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œλŠ” '비동기 ν•¨μˆ˜'라고 ν•©λ‹ˆλ‹€.

 

비동기 처리의 ν•œκ³„μ μ€ μž‘μ—… μ™„λ£Œ μ‹œμ μ„ μ˜ˆμΈ‘ν•˜κΈ° μ–΄λ ΅λ‹€λŠ” μ μž…λ‹ˆλ‹€.

ν•˜μ§€λ§Œ 이것은 비동기 처리의 본질적인 νŠΉμ„±μ΄λ©°, 이λ₯Ό 효과적으둜 κ΄€λ¦¬ν•˜λŠ” 것이 μ€‘μš”ν•©λ‹ˆλ‹€.

 

μžλ°”μŠ€ν¬λ¦½νŠΈλŠ” 기본적으둜 μ‹±κΈ€ μŠ€λ ˆλ“œ(single-threaded) 기반의 μ–Έμ–΄λ‘œ, ν•œ λ²ˆμ— ν•˜λ‚˜μ˜ μž‘μ—…λ§Œ μ²˜λ¦¬ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

즉, μžλ°”μŠ€ν¬λ¦½νŠΈλŠ” μ½”λ“œκ°€ μž‘μ„±λœ μˆœμ„œλŒ€λ‘œ, μœ„μ—μ„œ μ•„λž˜λ‘œ, λ™κΈ°μ μœΌλ‘œ μ²˜λ¦¬λœλ‹€λŠ” 것을 μ˜λ―Έν•©λ‹ˆλ‹€.

κ·Έλ ‡λ‹€λ©΄ μ–΄λ–»κ²Œ μžλ°”μŠ€ν¬λ¦½νŠΈκ°€ 비동기적인 λ™μž‘μ„ κ°€λŠ₯ν•˜κ²Œ ν•  수 μžˆμ„κΉŒμš”?
이에 λŒ€ν•œ μžμ„Έν•œ λ‚΄μš©μ€ ν•΄λ‹Ή κΈ€(Event Loop)을 μ°Έκ³ ν•΄ μ£Όμ„Έμš”.

 

이번 ν¬μŠ€νŒ…μ€ 제λͺ© κ·ΈλŒ€λ‘œ 비동기λ₯Ό μ²˜λ¦¬ν•˜λŠ” νŒ¨ν„΄μ— λŒ€ν•΄μ„œ μ•Œμ•„λ³΄λŠ” μ‹œκ°„μ„ κ°–μŠ΅λ‹ˆλ‹€.

 

비동기λ₯Ό μ²˜λ¦¬ν•˜λŠ” νŒ¨ν„΄μ—λŠ” μ•„λž˜μ™€ 같이 μžˆμŠ΅λ‹ˆλ‹€.

  • 콜백(Callback) ν•¨μˆ˜
  • ν”„λ‘œλ―ΈμŠ€(Promise)
  • μ œλ„ˆλ ˆμ΄ν„°(Generator)
  • async/await

μœ„ νŒ¨ν„΄λ“€μ— λŒ€ν•΄μ„œ μ–΄λ–»κ²Œ μ‚¬μš©λ˜κ³  μ™œ λ“±μž₯ν–ˆλŠ”μ§€ μ•Œμ•„λ΄…μ‹œλ‹€.

비동기 처리 νŒ¨ν„΄ ⭐

콜백(Callback) ν•¨μˆ˜ νŒ¨ν„΄ πŸ’‘

λ¨Όμ €, μ½œλ°±ν•¨μˆ˜λž€ μ–΄λ–€ ν•¨μˆ˜μ˜ 인자둜 μ „λ‹¬λ˜λŠ” ν•¨μˆ˜λ₯Ό μ˜λ―Έν•©λ‹ˆλ‹€.

μ΄λŸ¬ν•œ 콜백 ν•¨μˆ˜λŠ” 특히 비동기 처리λ₯Ό μœ„ν•΄ μ‚¬μš©λ˜λ©°, 이 경우 비동기 콜백이라고 λΆˆλ¦½λ‹ˆλ‹€.

콜백 ν•¨μˆ˜ νŒ¨ν„΄
νŠΉμ • 연산이 μ™„λ£Œλœ 이후에 싀행될 μ½”λ“œλ₯Ό ν•¨μˆ˜ ν˜•νƒœλ‘œ μ •μ˜ν•˜λŠ” λ°©μ‹μž…λ‹ˆλ‹€.
κ·Έλž˜μ„œ μ΄λŸ¬ν•œ 방식은 μžλ°”μŠ€ν¬λ¦½νŠΈμ˜ 초기 μ‹œμ ˆλΆ€ν„° 널리 μ‚¬μš©λ˜μ–΄ μ™”μŠ΅λ‹ˆλ‹€.

콜백 ν•¨μˆ˜ μ˜ˆμ‹œ

μš°λ¦¬λŠ” 3μ΄ˆκ°€ κ±Έλ¦¬λŠ” μž‘μ—…μ΄ μžˆλ‹€κ³  κ°€μ •ν•©λ‹ˆλ‹€.

setTimeoutν•¨μˆ˜λ₯Ό μ΄μš©ν•΄μ„œ ν•΄λ‹Ή μž‘μ—…μ΄ μ™„λ£Œλœ ν›„ λ‹€μŒ μ½”λ“œλ₯Ό λ™μž‘ν•˜λ„λ‘ ν•˜κ³  μ‹ΆμŠ΅λ‹ˆλ‹€.

function getData() {
  setTimeout(() => {
    console.log('데이터λ₯Ό λ°›μ•˜μŠ΅λ‹ˆλ‹€.');
  }, 3000);
}

getData();
console.log('μž‘μ—…μ΄ λλ‚¬μŠ΅λ‹ˆλ‹€!');

// 좜λ ₯ μˆœμ„œ
// 1. 'μž‘μ—…μ΄ λλ‚¬μŠ΅λ‹ˆλ‹€!'
// 2. '데이터λ₯Ό λ°›μ•˜μŠ΅λ‹ˆλ‹€.'

μœ„ μ½”λ“œλ₯Ό 보면 μžλ°”μŠ€ν¬λ¦½νŠΈλŠ” λ™κΈ°μ μœΌλ‘œ μž‘λ™ν•˜λŠ” 언어인데 μ™œ 좜λ ₯ μˆœμ„œκ°€ λ§ˆμ§€λ§‰μ— μžˆλŠ” 'μž‘μ—…μ΄ λλ‚¬μŠ΅λ‹ˆλ‹€!'κ°€ λ¨Όμ € 좜λ ₯λ˜μ—ˆμ„κΉŒμš”?

 

λ°”λ‘œ setTimeout은 λΉ„λ™κΈ°μ μœΌλ‘œ μ‹€ν–‰λ˜λŠ” 비동기 ν•¨μˆ˜μ΄κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.


setTimeout이 λΉ„λ™κΈ°μ μœΌλ‘œ 싀행될 수 μžˆλŠ” μ΄μœ λŠ” λ°”λ‘œ setTimeout은 WebApisμ—μ„œ κ΄€λ¦¬λ˜κ³  λ©€ν‹° μŠ€λ ˆλ“œ ν™˜κ²½μ—μ„œ μ‹€ν–‰λ˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€. (이에 κ΄€λ ¨ν•΄μ„œλŠ” Event Loop의 지식이 ν•„μš”ν•©λ‹ˆλ‹€.)

 

setTiemout이 λΉ„λ™κΈ°μ μœΌλ‘œ μ‹€ν–‰λ˜κΈ° λ•Œλ¬Έμ— setTimeout의 μž‘μ—…μ„ 기닀리지 μ•Šκ³  λ‹€μŒ μ½”λ“œμΈ 'μž‘μ—…μ΄ λλ‚¬μŠ΅λ‹ˆλ‹€'λΌλŠ” κ²°κ³Όκ°€ λ¨Όμ € 좜λ ₯λ˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

 

그런데, 이 κ²°κ³ΌλŠ” μš°λ¦¬κ°€ λ°”λΌλŠ” κ²°κ³Όκ°€ μ•„λ‹™λ‹ˆλ‹€.

'데이터λ₯Ό λ°›μ•˜μŠ΅λ‹ˆλ‹€.'λ₯Ό 좜λ ₯ν•˜κ³  κ·Έλ‹€μŒ μˆœμ„œλ‘œ 'μž‘μ—…μ΄ λλ‚¬μŠ΅λ‹ˆλ‹€!'λΌλŠ” κ²°κ³Όλ₯Ό 보렀면 μ–΄λ–»κ²Œ ν•΄μ•Ό ν• κΉŒμš”?

 

이럴 λ•Œ 비동기 μ½œλ°±μ„ μ΄μš©ν•΄μ„œ μ›ν•˜λŠ” κ²°κ³Όλ₯Ό 얻을 수 μžˆμŠ΅λ‹ˆλ‹€.

function getData(callback) {
  setTimeout(() => {
    console.log('데이터λ₯Ό λ°›μ•˜μŠ΅λ‹ˆλ‹€.');
    callback();
  }, 3000);
}

getData(() => console.log('μž‘μ—…μ΄ λλ‚¬μŠ΅λ‹ˆλ‹€!'));

// 좜λ ₯ μˆœμ„œ
// 1. '데이터λ₯Ό λ°›μ•˜μŠ΅λ‹ˆλ‹€.'
// 2. 'μž‘μ—…μ΄ λλ‚¬μŠ΅λ‹ˆλ‹€!'

μœ„ μ½”λ“œμ™€ 같이 getData ν•¨μˆ˜μ˜ 인자둜 μ½œλ°±ν•¨μˆ˜λ₯Ό μ „λ‹¬ν–ˆμŠ΅λ‹ˆλ‹€.

 

κ·Έλž˜μ„œ μ½”λ“œμ˜ μ‹€ν–‰μˆœμ„œλ₯Ό μ‚΄νŽ΄λ³΄λ©΄

  1. getData ν•¨μˆ˜μ— 'μž‘μ—…μ΄ λλ‚¬μŠ΅λ‹ˆλ‹€!'λ₯Ό 좜λ ₯ν•˜λŠ” 콜백 ν•¨μˆ˜ 전달 및 μ‹€ν–‰
  2. getData ν•¨μˆ˜μ˜ setTimeout ν•¨μˆ˜ μ‹€ν–‰
  3. 3μ΄ˆκ°€ μ§€λ‚œ λ’€ setTimeout의 콜백 ν•¨μˆ˜μ˜ μ½”λ“œλ₯Ό 순차적으둜 μ‹€ν–‰
  4. '데이터λ₯Ό λ°›μ•˜μŠ΅λ‹ˆλ‹€.' 좜λ ₯
  5. getData ν•¨μˆ˜μ— μ „λ‹¬λœ 콜백 ν•¨μˆ˜κ°€ μ‹€ν–‰λ˜μ–΄ 'μž‘μ—…μ΄ λλ‚¬μŠ΅λ‹ˆλ‹€!' 좜λ ₯

μ΄λ ‡κ²Œ μ›ν•˜λŠ” μž‘μ—…μ΄ μ™„λ£Œλœ 이후에 μ‹€ν–‰λ˜λ„λ‘, 즉 λΉ„λ™κΈ°μ μœΌλ‘œ λ™μž‘ν•˜λŠ” μ½”λ“œλ₯Ό λ™κΈ°μ μœΌλ‘œ μ²˜λ¦¬ν•  수 있게 λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

콜백 ν•¨μˆ˜μ˜ 문제점

ν•˜μ§€λ§Œ, μ—¬λŸ¬ 개의 μž‘μ—…μ„ μ²˜λ¦¬ν•˜λŠ” κ²½μš°μ— λ¬Έμ œκ°€ λ°œμƒν•©λ‹ˆλ‹€.

function firstTask(callback) {
  setTimeout(() => {
    console.log('첫번째 μž‘μ—… μ™„λ£Œ!');
    callback();
  }, 1500);
}

function secondTask(callback) {
  setTimeout(() => {
    console.log('λ‘λ²ˆμ§Έ μž‘μ—… μ™„λ£Œ!');
    callback();
  }, 1000);
}

function thirdTask() {
  setTimeout(() => {
    console.log('μ„Έλ²ˆμ§Έ μž‘μ—… μ™„λ£Œ!');
  }, 500);
}

firstTask(() => {
  secondTask(() => {
    thirdTask();
  });
});

μœ„ μ½”λ“œμ™€ 같이 점점 μ²˜λ¦¬ν•  뢀뢄이 λ§Žμ•„μ§„λ‹€λ©΄ μ•„λž˜μ™€ 같은 μ½”λ“œλ‘œ λ³€ν•˜κ²Œ λ©λ‹ˆλ‹€.

콜백 지μ˜₯

μœ„μ™€ 같은 ν˜„μƒμ„ λ°”λ‘œ '콜백 지μ˜₯(Callback Hell)'이라고 ν‘œν˜„ν•©λ‹ˆλ‹€.


콜백 지μ˜₯은 비동기 μž‘μ—…μ΄ μ„œλ‘œ μ—°κ²°λ˜μ–΄ μžˆκ±°λ‚˜ 순차적으둜 μ‹€ν–‰λ˜μ–΄μ•Ό ν•  λ•Œ, 콜백 ν•¨μˆ˜κ°€ μ€‘μ²©λ˜μ–΄ μ½”λ“œμ˜ κΉŠμ΄κ°€ κΉŠμ–΄μ§€κ³ , 이둜 μΈν•΄μ„œ 가독성이 떨어지며, μœ μ§€λ³΄μˆ˜κ°€ μ–΄λ €μ›Œμ§€λŠ” ν˜„μƒμž…λ‹ˆλ‹€.

Promise

μœ„μ—μ„œ μ‚΄νŽ΄λ³Έ 콜백 지μ˜₯을 ν•΄κ²°ν•˜κΈ° μœ„ν•΄ ES6μ—μ„œ PromiseλΌλŠ” κ°œλ…μ΄ λ“±μž₯ν–ˆμŠ΅λ‹ˆλ‹€.

Promiseλž€?
비동기 μ²˜λ¦¬μ— μ‚¬μš©λ˜λŠ” μžλ°”μŠ€ν¬λ¦½νŠΈ 객체둜, 비동기 μž‘μ—…μ˜ μ΅œμ’… μ™„λ£Œ(λ˜λŠ” μ‹€νŒ¨)와 κ·Έ κ²°κ³Ό 값을 λ‚˜νƒ€λƒ…λ‹ˆλ‹€.

 

Promise κ°μ²΄λŠ” 'μƒνƒœ(state)'와 'κ°’(result)'λΌλŠ” 두 가지 속성을 가지고 μžˆμŠ΅λ‹ˆλ‹€.

μƒνƒœμ—λŠ” λŒ€κΈ°(pending), 이행(fulfilled), κ±°λΆ€(rejected)의 μ„Έ 가지 μƒνƒœκ°€ μžˆμŠ΅λ‹ˆλ‹€.

  • λŒ€κΈ°(pending): 아직 μž‘μ—…μ΄ μ™„λ£Œλ˜μ§€λ„ μ•Šμ•˜κ³ , μ‹€νŒ¨ν•˜μ§€λ„ μ•Šμ€ 초기 μƒνƒœ
  • 이행(fulfilled): μž‘μ—…μ΄ μ„±κ³΅μ μœΌλ‘œ μ™„λ£Œλœ μƒνƒœ
  • κ±°λΆ€(rejected): μž‘μ—…μ΄ μ‹€νŒ¨ν•œ μƒνƒœ

Promise 성곡 μƒνƒœ μ˜ˆμ‹œ

μœ„μ™€ 같이 μ˜ˆμ‹œλ‘œ, 성곡 μƒνƒœμΈ Promise 객체λ₯Ό 좜λ ₯ν•˜λ©΄, μƒνƒœλŠ” 'fulfilled'κ°€ 되고, κ²°κ³Ό κ°’μ—λŠ” 성곡 μ‹œ λ°˜ν™˜κ°’μ΄ λ‹΄κΉλ‹ˆλ‹€.

λŒ€κΈ° μƒνƒœμ—μ„œλŠ” κ²°κ³Ό 값에 'undefined'κ°€ λ‹΄κΈ°λ©°, μ‹€νŒ¨ μƒνƒœμ—λŠ” μ—λŸ¬μ˜ λ‚΄μš©μ΄ λ‹΄κΈ°κ²Œ λ©λ‹ˆλ‹€.

Promise κ°μ²΄λŠ” ν•œ 번 μ„±κ³΅ν•˜κ±°λ‚˜ μ‹€νŒ¨ν•˜λ©΄ κ·Έ μƒνƒœκ°€ κ³ μ •λ©λ‹ˆλ‹€. 이둜 μΈν•΄μ„œ μ•ˆμ •μ μœΌλ‘œ 비동기 처리λ₯Ό ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

λ˜ν•œ, PromiseλŠ” then(), catch(), finally() λ“±μ˜ λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€.

  • then(): Promiseκ°€ μ„±κ³΅μ μœΌλ‘œ 이행(μ™„λ£Œ)될 λ•Œ μ‹€ν–‰ν•  μ½”λ“œλ₯Ό μž‘μ„±ν•©λ‹ˆλ‹€.
  • catch(): μž‘μ—…μ΄ κ±°λΆ€(μ‹€νŒ¨)ν–ˆμ„ λ•Œ μ‹€ν–‰ν•  μ½”λ“œλ₯Ό μž‘μ„±ν•©λ‹ˆλ‹€.
  • finally(): μž‘μ—…μ˜ 성곡, μ‹€νŒ¨ 여뢀와 관계없이 λ§ˆμ§€λ§‰μ— 무쑰건 μ‹€ν–‰λ˜μ–΄μ•Ό ν•˜λŠ” μ½”λ“œλ₯Ό μž‘μ„±ν•©λ‹ˆλ‹€.

ν•΄λ‹Ή λ©”μ„œλ“œλ“€λ„ Promise 객체λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

 

그리고 Promise κ°μ²΄λŠ” 'executor'λΌλŠ” ν•¨μˆ˜λ₯Ό 인자둜 λ°›μ•„ μƒμ„±λ©λ‹ˆλ‹€.

이 executor ν•¨μˆ˜λŠ” new Promise 객체가 μƒμ„±λ˜μžλ§ˆμž μ‹€ν–‰λ˜λ©°, 두 개의 콜백 ν•¨μˆ˜λ₯Ό 인자둜 λ°›μŠ΅λ‹ˆλ‹€.

  • resolve: 비동기 μž‘μ—…μ΄ μ„±κ³΅μ μœΌλ‘œ μ™„λ£Œλ˜μ—ˆμ„ λ•Œ ν˜ΈμΆœλ˜λŠ” ν•¨μˆ˜. 이 ν•¨μˆ˜λ₯Ό 톡해 PromiseλŠ” 성곡(fulfilled) μƒνƒœκ°€ 됨.
  • reject: 비동기 μž‘μ—…μ— λ¬Έμ œκ°€ 생겼을 λ•Œ ν˜ΈμΆœλ˜λŠ” ν•¨μˆ˜. 이 ν•¨μˆ˜λ₯Ό 톡해 PromiseλŠ” κ±°λΆ€(rejected) μƒνƒœκ°€ 됨.

Promise 예제

μ•„μ£Ό 기본적인 Promise 예제λ₯Ό 보면 μ•„λž˜ μ½”λ“œμ™€ κ°™μŠ΅λ‹ˆλ‹€.

const promise = new Promise((resolve, reject) => {
  const success = true; // μ‹€μ œλ‘œλŠ” μ–΄λ–€ 비동기 μž‘μ—…μ˜ 결과에 따라 결정됨. μ‹€νŒ¨ 및 성곡에 λŒ€ν•œ μ˜ˆμ‹œμΌ λΏμž…λ‹ˆλ‹€.

  if (success) {
    // 비동기 μž‘μ—…μ΄ μ„±κ³΅ν•œ 경우
    resolve('μž‘μ—…μ΄ μ„±κ³΅μ μœΌλ‘œ μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.');
  } else {
    // 비동기 μž‘μ—…μ΄ μ‹€νŒ¨ν•œ 경우
    reject('μž‘μ—…μ— μ‹€νŒ¨ν•˜μ˜€μŠ΅λ‹ˆλ‹€.');
  }
});

promise
  .then((message) => {
    // Promiseκ°€ μ„±κ³΅λœ 경우 싀행될 ν•¨μˆ˜
    console.log('성곡 λ©”μ‹œμ§€: ' + message);
  })
  .catch((message) => {
    // Promiseκ°€ κ±°λΆ€λœ 경우 싀행될 ν•¨μˆ˜
    console.log('μ‹€νŒ¨ λ©”μ‹œμ§€: ' + message);
  })
  .finally(() => {
    // 성곡, μ‹€νŒ¨ 여뢀와 상관 없이 λ§ˆμ§€λ§‰μ— μ‹€ν–‰
    console.log('μž‘μ—…μ΄ λλ‚¬μŠ΅λ‹ˆλ‹€');
  });

 

그럼 이전에 비동기 μ½œλ°±μ„ Pomise둜 μ²˜λ¦¬ν•˜κ²Œ 되면 μ–΄λ–»κ²Œ λ°”λ€” 수 μžˆμ„κΉŒμš”?

λ°”λ‘œ μ•„λž˜μ™€ 같이 λ³€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

function firstTask() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('첫번째 μž‘μ—… μ™„λ£Œ!');
      resolve();
    }, 1500);
  });
}

function secondTask() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('λ‘λ²ˆμ§Έ μž‘μ—… μ™„λ£Œ!');
      resolve();
    }, 1000);
  });
}

function thirdTask() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('μ„Έλ²ˆμ§Έ μž‘μ—… μ™„λ£Œ!');
      resolve();
    }, 500);
  });
}

firstTask()
  .then(() => secondTask())
  .then(() => thirdTask())
  .catch((error) => console.log(error));

μœ„μ™€ 같이. then λ©”μ„œλ“œλ₯Ό μ—°μ†μ μœΌλ‘œ ν˜ΈμΆœν•˜μ—¬ μ—¬λŸ¬ 개의 Promiseλ₯Ό μ—°κ²°ν•˜λŠ” 방식을 Promise 체인이라고 ν•©λ‹ˆλ‹€.

Promise 체인을 ν†΅ν•΄μ„œ 비동기 μž‘μ—…μ˜ κ²°κ³Όλ₯Ό 이어받아 μ‚¬μš©ν•˜κ±°λ‚˜, μ—¬λŸ¬ 비동기 μž‘μ—…μ„ 순차적으둜 μ‹€ν–‰ν•˜μ—¬, κ΅¬μ‘°ν™”λœ μ½”λ“œλ₯Ό μž‘μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

μ•„λž˜ μ½”λ“œμ™€ 같이 더 κΉ”λ”ν•˜κ²Œ ν‘œν˜„ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

firstTask()
  .then(secondTask)
  .then(thirdTask)
  .catch(console.log);

 

ν•˜μ§€λ§Œ, 이런 방식은 체인이 κΈΈμ–΄μ§ˆμˆ˜λ‘ κ²°κ΅­ 가독성이 λ–¨μ–΄μ§€κ²Œ λ©λ‹ˆλ‹€.

function firstTask() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('첫번째 μž‘μ—… μ™„λ£Œ!');
      resolve('첫번째 μž‘μ—… κ²°κ³Ό');
    }, 1500);
  });
}

function secondTask(result) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('λ‘λ²ˆμ§Έ μž‘μ—… μ™„λ£Œ!');
      resolve(result + ' -> λ‘λ²ˆμ§Έ μž‘μ—… κ²°κ³Ό');
    }, 1000);
  });
}

function thirdTask(result) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('μ„Έλ²ˆμ§Έ μž‘μ—… μ™„λ£Œ!');
      resolve(result + ' -> μ„Έλ²ˆμ§Έ μž‘μ—… κ²°κ³Ό');
    }, 500);
  });
}

function fourthTask(result) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('λ„€λ²ˆμ§Έ μž‘μ—… μ™„λ£Œ!');
      resolve(result + ' -> λ„€λ²ˆμ§Έ μž‘μ—… κ²°κ³Ό');
    }, 200);
  });
}

firstTask()
  .then((result) => {
    // 좔가적인 μž‘μ—…μ„ μˆ˜ν–‰ν•˜κ³  κ·Έ κ²°κ³Όλ₯Ό λ‹€μŒ Promise둜 전달
    const processedResult = result + ' -> 쀑간 처리';
    console.log(`첫번째 μž‘μ—… ν›„ 처리: ${processedResult}`);
    return secondTask(processedResult);
  })
  .then((result) => {
    // 좔가적인 μž‘μ—…μ„ μˆ˜ν–‰ν•˜κ³  κ·Έ κ²°κ³Όλ₯Ό λ‹€μŒ Promise둜 전달
    const processedResult = result + ' -> 쀑간 처리';
    console.log(`λ‘λ²ˆμ§Έ μž‘μ—… ν›„ 처리: ${processedResult}`);
    return thirdTask(processedResult);
  })
  .then((result) => {
    // 좔가적인 μž‘μ—…μ„ μˆ˜ν–‰ν•˜κ³  κ·Έ κ²°κ³Όλ₯Ό λ‹€μŒ Promise둜 전달
    const processedResult = result + ' -> 쀑간 처리';
    console.log(`μ„Έλ²ˆμ§Έ μž‘μ—… ν›„ 처리: ${processedResult}`);
    return fourthTask(processedResult);
  })
  .then((finalResult) => console.log(`μ΅œμ’… κ²°κ³Ό: ${finalResult}`))
  .catch((error) => console.log(error));

PromiseλŠ” 콜백 지μ˜₯ 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ λ„μž…λ˜μ—ˆμœΌλ‚˜, λ©”μ„œλ“œ 체이닝을 ν†΅ν•œ 연속적인 비동기 μž‘μ—… μ²˜λ¦¬λŠ” 가독성을 μ—¬μ „νžˆ λ–¨μ–΄λœ¨λ¦΄ 수 μžˆμŠ΅λ‹ˆλ‹€.

 

ν•˜μ§€λ§Œ 이런 단점을 κ°μˆ˜ν•˜λ”λΌλ„, PromiseλŠ” κ·Έ 자체둜 큰 κ°€μΉ˜λ₯Ό 가지고 μžˆμŠ΅λ‹ˆλ‹€.

λ‚˜λ¦„μ˜ ν‘œμ€€μ„ 정해놓고 κ²°κ³Όλ₯Ό μ˜ˆμΈ‘ν•˜κΈ° μ‰½κ²Œ λ§Œλ“€μ–΄μ£Όλ©°, μ½”λ“œμ˜ 신뒰성을 λ†’μ΄λŠ” 데 크게 κΈ°μ—¬ν•˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.

μ œλ„ˆλ ˆμ΄ν„°(Generator)

μ œλ„ˆλ ˆμ΄ν„° λ˜ν•œ ES6에 λ„μž…λœ κ°œλ…μž…λ‹ˆλ‹€.

μ œλ„ˆλ ˆμ΄ν„°λž€?
μ œλ„ˆλ ˆμ΄ν„°λŠ” μ½”λ“œ λΈ”λ‘μ˜ 싀행을 μΌμ‹œ 쀑지 ν–ˆλ‹€κ°€ ν•„μš”ν•œ μ‹œμ μ— μž¬κ°œν•  수 μžˆλŠ” νŠΉμˆ˜ν•œ ν•¨μˆ˜μž…λ‹ˆλ‹€.
일반 ν•¨μˆ˜μ™€λŠ” λ‹€λ₯΄κ²Œ, μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜λ©΄ λ°”λ‘œ ν•¨μˆ˜μ˜ λ‚΄μš©μ΄ μ‹€ν–‰λ˜μ§€ μ•Šκ³ , μ œλ„ˆλ ˆμ΄ν„° 객체가 λ°˜ν™˜λ©λ‹ˆλ‹€.

 

μ œλ„ˆλ ˆμ΄ν„° κ°μ²΄λŠ” μ΄ν„°λŸ¬λΈ”(iterable)μ΄λ©΄μ„œ λ™μ‹œμ— μ΄ν„°λ ˆμ΄ν„°(iterator)μž…λ‹ˆλ‹€.

  • μ΄ν„°λŸ¬λΈ”(iteralble): 반볡 κ°€λŠ₯ν•œ 자료ꡬ쑰λ₯Ό μ˜λ―Έν•©λ‹ˆλ‹€. μ—¬κΈ°μ„œ 반볡 κ°€λŠ₯ν•˜λ‹€λŠ” 것은 각 μš”μ†Œλ₯Ό ν•œ λ²ˆμ— ν•˜λ‚˜μ”© μ²˜λ¦¬ν•  수 μžˆλ‹€λŠ” λœ»μž…λ‹ˆλ‹€. μ΄ν„°λŸ¬λΈ” κ°μ²΄λŠ” [Symbol.iterator] λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•΄μ•Ό ν•©λ‹ˆλ‹€. 이 λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λ©΄ μ΄ν„°λ ˆμ΄ν„°λ₯Ό λ°˜ν™˜ν•˜κ³ , λ°°μ—΄, λ¬Έμžμ—΄, Map, Set 등이 μ΄ν„°λŸ¬λΈ”μ— ν•΄λ‹Ήλ©λ‹ˆλ‹€. μ΄ν„°λŸ¬λΈ”μ„ μ‚¬μš©ν•˜λ©΄ for...of 반볡문, μŠ€ν”„λ ˆλ“œ μ—°μ‚°μž(...), ꡬ쑰 λΆ„ν•΄ ν• λ‹Ή λ“±μ—μ„œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • μ΄ν„°λ ˆμ΄ν„°(iterator): 'λ‹€μŒ' μš”μ†Œλ₯Ό λ°˜ν™˜ν•˜λŠ” next() λ©”μ„œλ“œλ₯Ό 가진 객체λ₯Ό μ˜λ―Έν•©λ‹ˆλ‹€. next() λ©”μ„œλ“œλŠ” {value, done} ν˜•νƒœμ˜ 객체λ₯Ό λ°˜ν™˜ν•˜λ©°, valueλŠ” λ‹€μŒ 값이고, done은 μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜κ°€ λͺ¨λ‘ μ‹€ν–‰λ˜μ–΄ μ’…λ£Œλ˜μ—ˆλŠ”μ§€μ˜ μ—¬λΆ€μž…λ‹ˆλ‹€.

μ œλ„ˆλ ˆμ΄ν„° μ‚¬μš© 방법

  • function* ν‚€μ›Œλ“œλ‘œ μ„ μ–Έλ©λ‹ˆλ‹€.
  • yield ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ ν•¨μˆ˜μ˜ 싀행을 μ€‘λ‹¨ν•˜κ³  μž¬κ°œν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • next λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λ©΄ μ œλ„ˆλ ˆμ΄ν„°κ°€ μ²˜μŒλΆ€ν„° μ‹€ν–‰λ˜κ±°λ‚˜, 이전에 yield ν‚€μ›Œλ“œμ—μ„œ λ©ˆμ·„λ˜ λΆ€λΆ„λΆ€ν„° μ‹€ν–‰λ©λ‹ˆλ‹€.
  • next λ©”μ„œλ“œμ— 인자λ₯Ό μ „λ‹¬ν•˜λ©΄, κ·Έ 값은 μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜ λ‚΄λΆ€μ—μ„œ yield ν‚€μ›Œλ“œμ˜ κ²°κ³Όκ°’μœΌλ‘œ μ‚¬μš©λ©λ‹ˆλ‹€.
  • yield ν‚€μ›Œλ“œλŠ” μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜μ˜ 싀행을 λ©ˆμΆ”κ³ , μ‹€ν–‰ κΆŒν•œμ„ ν˜ΈμΆœμžμ—κ²Œ μ–‘λ„ν•©λ‹ˆλ‹€.
  • 이후에 next λ©”μ„œλ“œκ°€ 호좜되면 μ‹€ν–‰ κΆŒν•œμ΄ λ‹€μ‹œ μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜μ—κ²Œ μ΄λ™ν•©λ‹ˆλ‹€.
  • ν•¨μˆ˜λŠ” yield ν‚€μ›Œλ“œ μ΄ν›„μ˜ λΆ€λΆ„λΆ€ν„° 싀행을 μ΄μ–΄κ°‘λ‹ˆλ‹€.
function* generatorFunction() {
  console.log('ν•¨μˆ˜ μ‹œμž‘');
  const x = yield 1;
  console.log('첫 번째 yield 이후 ' + x);
  yield 2;
  console.log('두 번째 yield 이후');
  return 3; // μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜κ°€ μ’…λ£Œλ˜λ©΄μ„œ λ°˜ν™˜ν•˜λŠ” κ°’
}

const generator = generatorFunction();

const one = generator.next(); // 'ν•¨μˆ˜ μ‹œμž‘'
console.log(one); // {value: 1, done: false}
const two = generator.next(99); // '첫 번째 yield 이후 99'
console.log(two); // {value: 2, done: false}
const three = generator.next(); // '두 번째 yield 이후'
console.log(three); // {value: 3, done: true}

μ΄λ ‡κ²Œ μ œλ„ˆλ ˆμ΄ν„°λ₯Ό μ‚¬μš©ν•˜λ©΄ ν•¨μˆ˜μ˜ 싀행을 μ„Έλ°€ν•˜κ²Œ μ œμ–΄ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

즉, 비동기 μž‘μ—…μ„ 동기적인 λ°©μ‹μœΌλ‘œ μž‘μ„±ν•  수 있게 ν•΄ μ€λ‹ˆλ‹€.

async/await

async/await은 ES8에 λ„μž…λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

ν”„λ‘œλ―ΈμŠ€(Promise)λ₯Ό 기반으둜 ν•˜λ©°, 이전보닀 λ”μš± 비동기 처리λ₯Ό μ‰½κ²Œ ν•  수 있게 λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

 

async/await은 기쑴에 비동기 처리λ₯Ό μœ„ν•΄ μ‚¬μš©λ˜λŠ” ν”„λ‘œλ―ΈμŠ€(Promise)와 μ œλ„ˆλ ˆμ΄ν„°(Generator)의 λ¬Έμ œμ μ„ ν•΄κ²°ν•˜κΈ° μœ„ν•΄ λ“±μž₯ν–ˆμŠ΅λ‹ˆλ‹€.

 

μ΄μ „μ˜ λ‚΄μš©μ— λŒ€ν•΄ λ¬Έμ œμ μ„ κ°„λ‹¨νžˆ μ •λ¦¬ν•˜μžλ©΄ μ•„λž˜μ™€ κ°™μŠ΅λ‹ˆλ‹€.

Promise의 문제점

  • 체이닝: ν”„λ‘œλ―ΈμŠ€κ°€ μ—°μ†μ μœΌλ‘œ μ΄μ–΄μ§€λŠ” 경우, μ½”λ“œκ°€ λ³΅μž‘ν•˜κ³  가독성이 떨어진닀.
  • μ˜ˆμ™Έ 처리: μ˜ˆμ™Έλ₯Ό 적절히 μ²˜λ¦¬ν•˜κΈ° μœ„ν•΄μ„œλŠ” 각 ν”„λ‘œλ―ΈμŠ€λ§ˆλ‹€ μ—λŸ¬ ν•Έλ“€λŸ¬λ₯΄ λΆ™μ–΄μ£Όμ–΄μ•Ό ν•œλ‹€. 이둜 인해 μ½”λ“œλ₯Ό λ³΅μž‘ν•˜κ²Œ λ§Œλ“ λ‹€.

μ œλ„ˆλ ˆμ΄ν„°μ˜ 문제점

  • μ΄ν•΄ν•˜κΈ° μ–΄λ ΅λ‹€: μ΄ν„°λŸ¬λΈ”κ³Ό μ΄ν„°λ ˆμ΄ν„°μ˜ κ°œλ…μ„ 이해해야 ν•˜λ©°, 이 μžμ²΄λ‘œλ„ μ‰¬μš΄ λ‚΄μš©μ€ μ•„λ‹ˆλ‹€.
  • 직접 μ œμ–΄: ν•¨μˆ˜μ˜ 싀행을 μ œμ–΄ν•˜κΈ° μœ„ν•΄μ„œλŠ” μ œλ„ˆλ ˆμ΄ν„° κ°μ±„μ˜ next λ©”μ„œλ“œλ₯Ό 직접 ν˜ΈμΆœν•΄μ•Ό ν•œλ‹€.

즉, async/await은 μœ„μ™€ 같은 λ¬Έμ œμ λ“€μ„ ν•΄κ²°ν•˜κ³ μž λ„μž…λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

ν”„λ‘œλ―ΈμŠ€λ₯Ό 기반으둜 ν•˜λ©°, μ œλ„ˆλ ˆμ΄ν„°μ™€ μœ μ‚¬ν•œ λ™μž‘ 방식을 κ°–κ³  μžˆμŠ΅λ‹ˆλ‹€.

async ν•¨μˆ˜

asyncλŠ” 비동기 ν•¨μˆ˜λ₯Ό μ •μ˜ν•  λ•Œμ‚¬μš©λ©λ‹ˆλ‹€.


항상 Promiseλ₯Ό λ°˜ν™˜ν•˜λ©°, λ§Œμ•½ λ°˜ν™˜ 값이 ν”„λ‘œλ―ΈμŠ€κ°€ μ•„λ‹ˆλΌλ©΄, Promise.resolve λ©”μ„œλ“œμ— μ˜ν•΄μ„œ μžλ™μœΌλ‘œ Promiseλ₯Ό κ°μ‹Έμ„œ λ°˜ν™˜ν•©λ‹ˆλ‹€.

async/await 예제

await ν‚€μ›Œλ“œ

await ν‚€μ›Œλ“œλŠ” async ν•¨μˆ˜ λ‚΄λΆ€μ—μ„œλ§Œ μ‚¬μš©μ΄ κ°€λŠ₯ν•©λ‹ˆλ‹€.

 

비동기 μž‘μ—…μ„ 마치 λ™κΈ°μ μœΌλ‘œ μˆ˜ν–‰ν•˜λŠ” κ²ƒμ²˜λŸΌ ν•  수 있으며, await ν‚€μ›Œλ“œλŠ” ν•¨μˆ˜μ˜ 진행을 멈μΆ₯λ‹ˆλ‹€.

즉, ν”„λ‘œλ―ΈμŠ€μ˜ μ™„λ£Œλ₯Ό κΈ°λ‹€λ¦¬κ²Œ λ©λ‹ˆλ‹€.

async function asyncFunction() {
    const value1 = await doSomethingAsync();
    const value2 = await doSomethingElseAsync(value1);
    const value3 = await doAnotherAsyncThing(value2);
    return doSomethingWith(value3);
}

μ£Όμ˜ν•  점 ❗
ν”„λ‘œλ―ΈμŠ€λ₯Ό λ°˜ν™˜ν•˜μ§€ μ•ŠλŠ” 연산에 awaitλ₯Ό μ‚¬μš©ν•˜λ©΄, κ·Έ 연산은 μ¦‰μ‹œ ν•΄κ²°λœ Promise둜 κ°„μ£Όλ˜μ–΄ await ν‚€μ›Œλ“œκ°€ λ³„λ„μ˜ 효과λ₯Ό 갖지 μ•Šκ²Œ λ©λ‹ˆλ‹€.

μ‚¬μš© μ˜ˆμ‹œ

async function runTasks() {
  try {
    const firstResult = await firstTask();
    console.log('첫번째 μž‘μ—… κ²°κ³Ό:', firstResult);

    const secondResult = await secondTask(firstResult);
    console.log('λ‘λ²ˆμ§Έ μž‘μ—… κ²°κ³Ό:', secondResult);

    const thirdResult = await thirdTask(secondResult);
    console.log('μ„Έλ²ˆμ§Έ μž‘μ—… κ²°κ³Ό:', thirdResult);

    const fourthResult = await fourthTask(thirdResult);
    console.log('λ„€λ²ˆμ§Έ μž‘μ—… κ²°κ³Ό:', fourthResult);
  } catch (error) {
    console.error('μž‘μ—… 쀑 μ—λŸ¬ λ°œμƒ:', error);
  }
}

runTasks();

각각의 μž‘μ—…μ€ await ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ ν”„λ‘œλ―ΈμŠ€κ°€ 이행될 λ•ŒκΉŒμ§€ κΈ°λ‹€λ¦½λ‹ˆλ‹€.

λ§Œμ•½ ν”„λ‘œλ―ΈμŠ€κ°€ κ±°λΆ€λ˜λ©΄, catch λΈ”λ‘μ—μ„œ μ—λŸ¬λ₯Ό μ²˜λ¦¬ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ΄λ ‡κ²Œ async/await을 μ‚¬μš©ν•˜λ©΄ 비동기 μž‘μ—…μ˜ 흐름을 동기적인 λ°©μ‹μœΌλ‘œ μ‰½κ²Œ ν‘œν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

마무리 πŸ“•

μ½œλ°±ν•¨μˆ˜λΆ€ν„° async/awaitκΉŒμ§€ 기쑴의 비동기 μ²˜λ¦¬μ— μžˆμ–΄μ„œ μ–΄λ–€ λΆˆνŽΈν•¨μ΄ μžˆμ—ˆκ³  μ–΄λ–»κ²Œ κ°œμ„ λ˜μ—ˆλŠ”μ§€μ— λŒ€ν•΄ μ•Œμ•„λ³΄λŠ” μ‹œκ°„μ΄μ—ˆμŠ΅λ‹ˆλ‹€.

 

μ°Έκ³ λ¬Έν—Œ:

λ°˜μ‘ν˜•