ES6 μ œλ„ˆλ ˆμ΄ν„°μ˜ μˆ¨κ²¨μ§„ 힘: Observable Async 흐름 μ œμ–΄

  • 이 글은 The Hidden Power of ES6 Generators: Observable Async Flow Controlλ₯Ό λ²ˆμ—­ν•œ κΈ€μž…λ‹ˆλ‹€.
  • 아직 μž…λ¬Έμžμ΄λ‹€λ³΄λ‹ˆ μ˜€μ—­μ„ ν•œ κ²½μš°κ°€ μžˆμ„ 수 μžˆμŠ΅λ‹ˆλ‹€. μ–‘ν•΄ λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.
  • λ§€λ„λŸ¬μš΄ λ¬Έλ§₯을 μœ„ν•˜μ—¬ μ˜μ—­μ„ ν•œ κ²½μš°κ°€ μžˆμŠ΅λ‹ˆλ‹€. μ›λ¬Έμ˜ λœ»μ„ μ΅œλŒ€ν•œ ν•΄μΉ˜μ§€ μ•Šλ„λ‘ λ…Έλ ₯ν–ˆμœΌλ‹ˆ μ•ˆμ‹¬ν•˜μ…”λ„ λ©λ‹ˆλ‹€.
  • μ˜μ–΄ 단어가 μžμ—°μŠ€λŸ¬μš΄ 경우 원문 κ·ΈλŒ€λ‘œμ˜ μ˜λ‹¨μ–΄λ₯Ό μ μ—ˆμŠ΅λ‹ˆλ‹€.
  • μ €μ˜ 보좩 μ„€λͺ…은 μΈμš©λ¬Έμ— λ‹¬μ•˜μŠ΅λ‹ˆλ‹€.

ν”Όλ³΄λ‚˜μΉ˜ μ œλ„ˆλ ˆμ΄ν„°λ₯Ό μžλ°”μŠ€ν¬λ¦½νŠΈλ‘œ μž‘μ„±ν•˜λ©° 깨달은 λ†€λΌμš΄ 점 7κ°€μ§€μ—μ„œλŠ” ES6 μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜μ˜ ν™•μ‹€ν•œ μ‚¬μš©λ‘€λ₯Ό λ‹€λ€˜μŠ΅λ‹ˆλ‹€. ν•œλ²ˆμ— ν•˜λ‚˜μ”© λ°˜λ³΅ν•  수 μžˆλŠ”(Iterable) κ°’λ“€λ‘œ 이루어진 μˆ˜μ—΄μ„ λ§Œλ“œλŠ” κ²ƒμ΄μ—ˆλŠ”λ°μš”. 아직 κ·Έ 글을 읽지 μ•ŠμœΌμ…¨λ‹€λ©΄, λ°˜λ“œμ‹œ μ½μœΌμ…”μ•Ό ν•©λ‹ˆλ‹€! 반볡 κ°€λŠ₯ν•œ κ°μ²΄λŠ” ES6+λ₯Ό κ΅¬μ„±ν•˜λŠ” λ‹€μ–‘ν•œ κΈ°λŠ₯λ“€μ˜ 바탕을 이루고 있기 λ•Œλ¬Έμ—, μ–΄λ–»κ²Œ μž‘λ™ν•˜λŠ”μ§€λ₯Ό κΌ­ μ΄ν•΄ν•˜κ³  μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€.

ν•˜μ§€λ§Œ κ·Έ κΈ€μ—μ„œλŠ” μΌλΆ€λŸ¬, μ œλ„ˆλ ˆμ΄ν„°μ˜ κ°€μž₯ μ£Όμš”ν•œ μ‚¬μš© 사둀λ₯Ό 닀루지 μ•Šμ•˜μŠ΅λ‹ˆλ‹€. 감히 λ§ν•˜κ±΄λ°, 그것은 λ°”λ‘œ 비동기 흐름 μ œμ–΄μž…λ‹ˆλ‹€.

Async / Await

아직 μžλ°”μŠ€ν¬λ¦½νŠΈ 곡식 ν‘œμ€€ κΈ°λŠ₯으둜 μ±„νƒλ˜μ§€λŠ” μ•Šμ•˜μ§€λ§Œ, async/await에 λŒ€ν•˜μ—¬ 듀어보셨을 κ²λ‹ˆλ‹€. ES6μ—μ„œλ„ λͺ»ν–ˆκ³ , ES2016μ—μ„œλ„ λͺ»ν•  것이고, ES2017μ—μ„œλŠ” 될 지도 λͺ¨λ¦…λ‹ˆλ‹€. 그리고 우리 일반 μ‚¬μš©μžλ“€μ€ μ‹€μ§ˆμ μœΌλ‘œ μ‚¬μš©ν•  수 μžˆλ„λ‘ 되기 전에, λͺ¨λ“  μžλ°”μŠ€ν¬λ¦½νŠΈ 엔진듀이 κ΅¬ν˜„ν•˜κΈ°λ₯Ό κΈ°λ‹€λ €μ•Όλ§Œ ν•˜μ£ . (μ°Έκ³ : μ§€κΈˆμ€ Babelμ—μ„œ μ‚¬μš© κ°€λŠ₯ν•˜μ§€λ§Œ, 계속 μž₯λ‹΄ν•  μˆ˜λŠ” μ—†μŠ΅λ‹ˆλ‹€. 꼬리 호좜 μ΅œμ ν™”κ°™μ€ 경우 λͺ‡ κ°œμ›” μ „λ§Œ ν•˜λ”λΌλ„ Babelμ—μ„œ μ‚¬μš© κ°€λŠ₯ν–ˆμ—ˆμ§€λ§Œ κ²°κ΅­μ—” μ œκ±°λ˜μ—ˆμŠ΅λ‹ˆλ‹€.)

원문이 2016λ…„ 5μ›” 21일에 μž‘μ„±λ˜μ—ˆμœΌλ―€λ‘œ, 원문 μž‘μ„± λ‹Ήμ‹œ μ΅œμ‹  μžλ°”μŠ€ν¬λ¦½νŠΈ 버전은 ES6μ΄μ—ˆκ² κ΅°μš”. ν‘œμ€€ μžλ°”μŠ€ν¬λ¦½νŠΈ(ES)의 μ΅œμ‹  κ°œμ •μ€ μ „ν†΅μ μœΌλ‘œ 맀년 6월에 μ΄λ£¨μ–΄μ§‘λ‹ˆλ‹€.

이런 기닀림에도 λΆˆκ΅¬ν•˜κ³ , async/await에 λŒ€ν•˜μ—¬ λ‹€λ£¨λŠ” 글은 ν˜„μž¬ μƒλ‹Ήνžˆ λ§ŽμŠ΅λ‹ˆλ‹€. μ™œμΌκΉŒμš”?

μ•„λž˜μ™€ 같은 μ½”λ“œλ₯Ό:

const fetchSomething = () => new Promise((resolve) => {
  setTimeout(() => resolve('future value'), 500);
});

const promiseFunc = () => new Promise((resolve) => {
  fetchSomething().then(result => {
    resolve(result + ' 2');
  });
});

promiseFunc().then(res => console.log(res));

μ•„λž˜μ™€ 같이 λ°”κΏ”μ£ΌκΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€:

const fetchSomething = () => new Promise((resolve) => {
  setTimeout(() => resolve('future value'), 500);
});

async function asyncFunction() {
  const result = await fetchSomething(); // ν”„λΌλ―ΈμŠ€λ₯Ό λ°˜ν™˜

  // ν”„λΌλ―ΈμŠ€λ₯Ό κΈ°λ‹€λ Έλ‹€κ°€, ν”„λΌλ―ΈμŠ€μ˜ ν•΄κ²°λœ κ²°κ³Όλ₯Ό μ‚¬μš©
  return result + ' 2';
}

asyncFunction().then(result => console.log(result));

첫번째 μ½”λ“œμ—μ„œ ν”„λΌλ―ΈμŠ€ 기반의 ν•¨μˆ˜κ°€ 쀑첩을 ν•œ 단계 더 λ§Œλ“€μ—ˆλ‹€λŠ” 점에 μ£Όλͺ©ν•˜μ‹œκΈ° λ°”λžλ‹ˆλ‹€. async/await 버전은 일반적인 동기 μ½”λ“œμ²˜λŸΌ λ³΄μ΄μ§€λ§Œ, 사싀은 그렇지 μ•ŠμŠ΅λ‹ˆλ‹€. ν”„λΌλ―ΈμŠ€λ₯Ό μ‚°μΆœ(yield)ν•˜κ³  ν•¨μˆ˜λ₯Ό μž μ‹œ λ©ˆμΆ”μ–΄μ„œ μžλ°”μŠ€ν¬λ¦½νŠΈ 엔진이 마음 놓고 λ‹€λ₯Έ μž‘μ—…μ„ ν•  수 μžˆλ„λ‘ λ†“μ•„μ€λ‹ˆλ‹€. 그리고 fetchSomething()이 μ‚°μΆœν•œ ν”„λΌλ―ΈμŠ€κ°€ 값을 μ •μƒμ μœΌλ‘œ κ°€μ Έμ™”λ‹€λ©΄(resolve의 μ‹€ν–‰), μ•„κΉŒ λ©ˆμ·„λ˜ ν•¨μˆ˜κ°€ λ‹€μ‹œ λ™μž‘μ„ μž¬κ°œν•˜κ³ , ν”„λΌλ―ΈμŠ€μ˜ ν•΄κ²°λœ κ²°κ³Ό 값은 result λ³€μˆ˜μ— ν• λ‹Ήλ©λ‹ˆλ‹€.

마치 λ™κΈ°μ μœΌλ‘œ λ³΄μ΄λŠ” 비동기적인 μ½”λ“œμž…λ‹ˆλ‹€. 맀일 샐 수 없이 λ§Žμ€ 비동기 ν”„λ‘œκ·Έλž˜λ°μ„ ν•˜λŠ” μžλ°”μŠ€ν¬λ¦½νŠΈ ν”„λ‘œκ·Έλž˜λ¨Έμ—κ²Œ 이것은 κ·Έμ•Όλ§λ‘œ μ„±λ°° κ·Έ μžμ²΄μž…λ‹ˆλ‹€. 인지적인 κ³ΌλΆ€ν•˜κ°€ μ „ν˜€ 없이 비동기 μ½”λ“œμ˜ μ„±λŠ₯상 이점을 λͺ¨λ‘ μ·¨ν•  수 있기 λ•Œλ¬Έμž…λ‹ˆλ‹€.

μ›λ¬Έμ˜ yieldλŠ” μ‚°μΆœλ‘œ λ²ˆμ—­ν•˜μ˜€μŠ΅λ‹ˆλ‹€. λ˜ν•œ μ›λ¬Έμ—μ„œ resolveλŠ” ν•΄κ²°λ‘œ λ²ˆμ—­ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

μ—¬κΈ°μ„œ μ’€ 더 μ•Œμ•„λ³΄κ³ μž ν•˜λŠ” 것은 λ°”λ‘œ async/awaitκ°€ λ°°ν›„μ—μ„œ μ–΄λ–»κ²Œ μ œλ„ˆλ ˆμ΄ν„°λ₯Ό ν™œμš©ν•˜λŠ”μ§€... 그리고, async/awaitκ°€ λ‹Ήμž₯ μ—†λŠ” μ§€κΈˆ, 동기적인 μŠ€νƒ€μΌλ‘œ 흐름 관리λ₯Ό ν•  λ•Œμ— μ œλ„ˆλ ˆμ΄ν„°λ₯Ό μ–΄λ–»κ²Œ ν™œμš©ν•  수 μžˆλŠ”μ§€μ— λŒ€ν•œ κ²ƒμž…λ‹ˆλ‹€.

μ œλ„ˆλ ˆμ΄ν„° λ³΅μŠ΅ν•˜κΈ°

μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜λŠ” ES6의 μƒˆλ‘œμš΄ κΈ°λŠ₯으둜, 이 ν•¨μˆ˜λŠ” 계속 반볡될 수 μžˆλŠ” 객체λ₯Ό λ°˜ν™˜ν•¨μœΌλ‘œμ¨ μ‹œκ°„μ΄ 지남에 따라 κ³„μ†ν•΄μ„œ μ—¬λŸ¬ 값듀을 생성해낼 수 μžˆμŠ΅λ‹ˆλ‹€. μ—¬κΈ°μ„œ λ°˜ν™˜λ˜λŠ” κ°μ²΄λŠ” 반볡 κ°€λŠ₯ν•œ 객체둜, 이 κ°μ²΄λŠ” .next() λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λ©΄ μ•„λž˜μ™€ 같은 객체λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€:

{
  value: Any,
  done: Boolean
}
  • μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜ 호좜 =λ°˜ν™˜=> 반볡자 객체 =.next()호좜=> { value, done }
  • ν•œ λ°˜λ³΅μžμ— λŒ€ν•˜μ—¬ 반볡적으둜 .next() 호좜 κ°€λŠ₯

done 속성은 μ œλ„ˆλ ˆμ΄ν„°κ°€ λ§ˆμ§€λ§‰ 값을 μ‚°μΆœν•΄λƒˆλŠ”μ§€ μ—¬λΆ€λ₯Ό μ•Œλ €μ€λ‹ˆλ‹€.

반볡자 ν”„λ‘œν† μ½œμ€ μžλ°”μŠ€ν¬λ¦½νŠΈμ˜ λ‹€μ–‘ν•œ κ²ƒλ“€μ—μ„œ μ‚¬μš©λ˜κ³  μžˆλŠ”λ°, κ·Έ μ€‘μ—λŠ” for...of 반볡문, λ°°μ—΄μ˜ ... μ—°μ‚°μž 등이 ν¬ν•¨λ©λ‹ˆλ‹€.

function* foo() {
  yield 'a';
  yield 'b';
  yield 'c';
}

for (const val of foo()) {
  console.log(val);
}
// a
// b
// c

const [...values] = foo();
console.log(values); // ['a','b','c']

λ‹€μ‹œ μ œλ„ˆλ ˆμ΄ν„°μ˜ μ΄μ•ΌκΈ°λ‘œ λŒμ•„κ°€μ„œ

μ΄μ œλΆ€ν„° μž¬λ―Έμžˆμ–΄μ§‘λ‹ˆλ‹€. μ œλ„ˆλ ˆμ΄ν„°μ™€μ˜ 톡신은 μ–‘λ°©ν–₯으둜 μ΄λ£¨μ–΄μ§‘λ‹ˆλ‹€. μ œλ„ˆλ ˆμ΄ν„°λ‘œλΆ€ν„° 값을 λ°›λŠ” 것뿐 μ•„λ‹ˆλΌ, μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜μ— 값을 전달할 μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. μ œλ„ˆλ ˆμ΄ν„°μ— 값을 μ „λ‹¬ν•˜λ €λ©΄, 반볡자의 .next() λ©”μ„œλ“œμ˜ 인자λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

function* crossBridge() {
  const reply = yield 'What is your favorite color?';
  console.log(reply);
  if (reply !== 'yellow') return 'Wrong!'
  return 'You may pass.';
}

{
  const iter = crossBridge();
  const q = iter.next().value; // λ°˜λ³΅μžκ°€ μ§ˆλ¬Έμ„ μ‚°μΆœν•œλ‹€
  console.log(q);
  const a = iter.next('blue').value; // 닡변을 μ œλ„ˆλ ˆμ΄ν„°λ‘œ λ‹€μ‹œ μ „λ‹¬ν•œλ‹€
  console.log(a);
}

// What is your favorite color?
// blue
// Wrong!


{
  const iter = crossBridge();
  const q = iter.next().value;
  console.log(q);
  const a = iter.next('yellow').value;
  console.log(a);
}

// What is your favorite color?
// yellow
// You may pass.

[λͺ¬ν‹° 파이튼의 μ„±λ°° δΈ­ 죽음의 닀리]

μ œλ„ˆλ ˆμ΄ν„°μ™€ ν†΅μ‹ ν•˜λŠ” 방법은 λͺ‡ 가지 더 μ‘΄μž¬ν•©λ‹ˆλ‹€. 였λ₯˜λ₯Ό λ˜μ§€λŠ” 것도 ν•˜λ‚˜μ˜ λ°©λ²•μž…λ‹ˆλ‹€. .next()λ₯Ό ν˜ΈμΆœν•˜λŠ” λŒ€μ‹ , 예λ₯Ό λ“€μ–΄ iterator.throw(error)λ₯Ό ν˜ΈμΆœν•˜λ©΄ μ œλ„ˆλ ˆμ΄ν„°μ—μ„œ μ‚¬μš©ν•  데이터λ₯Ό κ°€μ Έμ˜€λŠ” 데에 무언가 λ¬Έμ œκ°€ μžˆμ—ˆμŒμ„ μ•Œλ¦΄ 수 μžˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ, iterator.return()을 ν˜ΈμΆœν•˜μ—¬ μ œλ„ˆλ ˆμ΄ν„°κ°€ κ°•μ œλ‘œ κ²°κ³Όλ₯Ό λ°˜ν™˜ν•˜λ„λ‘ λ§Œλ“€ μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

두 방법 λͺ¨λ‘ 흐름 μ œμ–΄ μ½”λ“œμ— 였λ₯˜ 처리 κΈ°λŠ₯을 μ μš©ν•˜λŠ” κ°„νŽΈν•œ λ°©λ²•μž…λ‹ˆλ‹€.

μ œλ„ˆλ ˆμ΄ν„° + ν”„λΌλ―ΈμŠ€ = μ„±λ°°

κ·Έλ ‡λ‹€λ©΄ 이런 ν•¨μˆ˜λŠ” μ–΄λ–€κ°€μš”? μƒˆλ‘œμš΄ ν”„λΌλ―ΈμŠ€κ°€ μƒμ„±λ˜λ©΄ 이λ₯Ό κ°μ§€ν•˜κ³ , 이 ν”„λΌλ―ΈμŠ€κ°€ 값을 μ •μƒμ μœΌλ‘œ κ°€μ Έμ˜¬ λ•ŒκΉŒμ§€ λŒ€κΈ°ν•œ λ’€, ν•΄κ²°λœ 값을 μ œλ„ˆλ ˆμ΄ν„°λ‘œ μ „ν•΄μ£Όλ©΄μ„œ .next()λ₯Ό ν˜ΈμΆœν•˜λŠ” ν•¨μˆ˜ 말이죠. 이 ν•¨μˆ˜λŠ” μ œλ„ˆλ ˆμ΄ν„°λ₯Ό λž˜ν•‘ν•˜λŠ” ν˜•νƒœκ°€ 될 κ²ƒμž…λ‹ˆλ‹€.

이런 ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜λ©΄, μ•„λž˜μ™€ 같은 async/await μŠ€νƒ€μΌμ˜ μ½”λ“œλ₯Ό μž‘μ„±ν•  수 있게 λ©λ‹ˆλ‹€:

const fetchSomething = () => new Promise((resolve) => {
  setTimeout(() => resolve('future value'), 500);
});

const asyncFunc = gensync(function* () {
  const result = yield fetchSomething(); // ν”„λΌλ―ΈμŠ€λ₯Ό λ°˜ν™˜

  // ν”„λΌλ―ΈμŠ€λ₯Ό κΈ°λ‹€λ Έλ‹€κ°€, ν”„λΌλ―ΈμŠ€μ˜ ν•΄κ²°λœ κ²°κ³Όλ₯Ό μ‚¬μš©
  yield result + ' 2';
});

// asyncFuncλ₯Ό ν˜ΈμΆœν•˜λ©΄μ„œ 인자λ₯Ό μ „λ‹¬ν•œλ‹€.
asyncFunc('param1', 'param2', 'param3')
  .then(val => console.log(val));

λ³΄μ•„ν•˜λ‹ˆ, 이런 κΈ°λŠ₯을 μˆ˜ν–‰ν•˜λŠ” Co.jsλΌλŠ” λΌμ΄λΈŒλŸ¬λ¦¬κ°€ 이미 μ‘΄μž¬ν•©λ‹ˆλ‹€. ν•˜μ§€λ§Œ Co의 μ‚¬μš©λ²•μ„ κ°€λ₯΄μ³λ“œλ¦¬λŠ” λŒ€μ‹ , Co와 같은 것을 직접 λ§Œλ“€μ–΄λ³΄λ„λ‘ ν•©μ‹œλ‹€. μœ„μ˜ crossBridge() 예제λ₯Ό 잘 μ‚΄νŽ΄λ³΄λ©΄, κ·Έλ ‡κ²Œ 어렡지 μ•Šμ•„ λ³΄μž…λ‹ˆλ‹€.

μ‹¬ν”Œν•œ isPromise() ν•¨μˆ˜λ‘œλΆ€ν„° μ‹œμž‘ν•˜λ„λ‘ ν•˜μ£ .

const isPromise = obj => Boolean(obj) && typeof obj.then === 'function';

λ‹€μŒμœΌλ‘œ, .next()λ₯Ό ν˜ΈμΆœν•˜μ—¬ μ œλ„ˆλ ˆμ΄ν„° λ‚΄λΆ€λ₯Ό μˆœνšŒν•˜κ³ , ν”„λΌλ―ΈμŠ€κ°€ ν•΄κ²°λœ 값을 κ°€μ Έμ˜€κΈ°λ₯Ό κΈ°λ‹€λ¦° λ’€ λ‹€μ‹œ .next()λ₯Ό ν˜ΈμΆœν•  수 μžˆλŠ” μˆ˜λ‹¨μ΄ ν•„μš”ν•˜κ² κ΅°μš”. μ•„λž˜μ˜ μ½”λ“œλŠ” μ΄λŸ¬ν•œ λ™μž‘μ„ μˆ˜ν–‰ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. λ‹€λ§Œ, 였λ₯˜ 처리 과정이 μƒλž΅λœ λ‹¨μˆœ κ°œλ… μ„€λͺ… μš©λ„μ˜ μ½”λ“œμž…λ‹ˆλ‹€. κ·ΈλŸ¬λ―€λ‘œ μ‹€μ œ κ°œλ°œμ—μ„œλŠ” μ‚¬μš©ν•˜μ§€ λ§ˆμ„Έμš”. 였λ₯˜κ°€ λ¬»ν˜€λ²„λ¦¬λ©΄, λ””λ²„κΉ…ν•˜λŠ” 것이 ꡉμž₯히 κ³¨μΉ˜μ•„νŒŒμ§ˆ κ²λ‹ˆλ‹€:

const next = (iter, callback, prev = undefined) => {
  const item = iter.next(prev);
  const value = item.value;

  if (item.done) return callback(prev);

  if (isPromise(value)) {
    value.then(val => {
      setImmediate(() => next(iter, callback, val));
    });
  } else {
    setImmediate(() => next(iter, callback, value));
  }
};

λ³΄μ‹œλŠ” 바와 같이. μ΅œμ’… κ²°κ³Όλ₯Ό λ°˜ν™˜ν•˜κΈ° μœ„ν•˜μ—¬ μ½œλ°±μ„ μ „λ‹¬ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. ν•¨μˆ˜μ˜ μ΅œμƒλ‹¨ 라인을 보면, λ°”λ‘œ μ§μ „μ˜ 값을 .next() ν˜ΈμΆœμ‹œ 인자둜 μ „λ‹¬ν•˜λŠ” κ²ƒμœΌλ‘œ μ œλ„ˆλ ˆμ΄ν„°μ™€ ν†΅μ‹ ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. μ΄λ ‡κ²Œ ν•˜λ©΄ 직전에 yield둜 전달받은 κ²°κ³Όλ₯Ό λ‹€μ‹œ μ œλ„ˆλ ˆμ΄ν„°λ‘œ 건넀쀄 수 있죠.

gensync ν•¨μˆ˜κ°€ λž˜ν•‘ν•œ μ œλ„ˆλ ˆμ΄ν„° λ‚΄λΆ€μ—μ„œ fetchSomething의 κ²°κ³Όκ°€ yieldλ₯Ό ν†΅ν•˜μ—¬ next ν•¨μˆ˜μ— μ „λ‹¬λ©λ‹ˆλ‹€. 이 값은 iter.next(prev).valueμž„μ„ μ•Œ 수 μžˆμ§€μš”. 이 값은 ν”„λΌλ―ΈμŠ€μ΄λ―€λ‘œ, .then()을 ν†΅ν•˜μ—¬ ν•΄λ‹Ή ν”„λΌλ―ΈμŠ€μ˜ κ²°κ³Όκ°€ nextκ°€ μž¬κ·€ 호좜될 λ•Œμ— 3번째 인자, val둜 μ „λ‹¬λ©λ‹ˆλ‹€. 그러면 λ‹€μ‹œ nextκ°€ 싀행될 λ•Œ, iter.next(prev)λ₯Ό ν†΅ν•˜μ—¬ μ œλ„ˆλ ˆμ΄ν„°λ‘œ μ „λ‹¬λ˜κ³ , μ œλ„ˆλ ˆμ΄ν„°μ—μ„œλŠ” κ·Έ λ‹€μŒ yield 싀행에 따라 2κ°€ 덧뢙여진 κ²°κ³Όλ₯Ό next에 돌렀주게 λ˜κ² μ§€μš”.

const next = (iter, callback, prev = undefined) => {
  // 2. μ‚°μΆœλœ 값은 `.next()`의 ν˜ΈμΆœμ„ ν†΅ν•˜μ—¬ μΆ”μΆœλœλ‹€.
  // μ§μ „μ˜ 값을 μ œλ„ˆλ ˆμ΄ν„°μ— μ „λ‹¬ν•˜κ²Œ 되고,
  // 이 값은 μ œλ„ˆλ ˆμ΄ν„° λ‚΄λΆ€μ—μ„œ `result` λ³€μˆ˜μ˜ κ°’ 할당에 μ‚¬μš©λœλ‹€.

  const item = iter.next(prev);
  const value = item.value;

  // 4. μ΅œμ’… 결과값은 콜백으둜 μ „λ‹¬λœλ‹€.
  if (item.done) return callback(prev);

  if (isPromise(value)) {
    value.then(val => {
      setImmediate(() => next(iter, callback, val));
    });
  } else {
    setImmediate(() => next(iter, callback, value));
  }
};

const asyncFunc = gensync(function* () {
  // 1. μ‚°μΆœλœ 값이 반볡자둜 μ „λ‹¬λœλ‹€.
  // yieldλ₯Ό λ§Œλ‚¬μ„ λ•Œμ— μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜λŠ” μΌμ‹œμ μœΌλ‘œ μ’…λ£Œλ˜κ³ ,
  // `result` λ³€μˆ˜μ— 값을 ν• λ‹Ήν•˜λŠ” μž‘μ—…μ€
  // μ œλ„ˆλ ˆμ΄ν„°κ°€ λ‹€μ‹œ 재개되기 μ „κΉŒμ§€ 이루어지지 μ•ŠλŠ”λ‹€.
  const result = yield fetchSomething();

  // 3. `.next()`κ°€ 호좜되기 μ „κΉŒμ§€λŠ” λ‹€μ‹œ μž‘λ™ν•˜μ§€ μ•ŠλŠ”λ‹€.
  // `result`λŠ” λ°”λ‘œ 직전 `.next()` ν˜ΈμΆœμ‹œ μ „λ‹¬λœ 값을 ν¬ν•¨ν•˜κ²Œ λœλ‹€.
  yield result + ' 2';
});

λ¬Όλ‘ , 직접 ν•¨μˆ˜λ₯Ό μ‹€ν–‰μ‹œν‚€κΈ° μ „κΉŒμ§€λŠ” 아무 일도 μΌμ–΄λ‚˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 그리고, μ‹€μ œ μ΅œμ’…κ°’μ„ λ°˜ν™˜ν•˜λŠ” ν”„λΌλ―ΈμŠ€λŠ” μ–΄λ”” μžˆμ„κΉŒμš”?

// ν”„λΌλ―ΈμŠ€λ₯Ό λ°˜ν™˜ν•˜κ³ , 졜초 `next()` ν˜ΈμΆœμ„ ν†΅ν•˜μ—¬
// λͺ¨λ“  μž‘μ—…μ„ μ‹œλ™ν•œλ‹€.
const gensync = (fn) =>
    (...args) => new Promise(resolve => {
  next(fn(...args), val => resolve(val));
});

gensync의 인자둜 μ „λ‹¬λœ fn이 μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜μž…λ‹ˆλ‹€. 컀링(currying)이 이루어져 μžˆμŒμ— μ£Όμ˜ν•˜μ„Έμš”.

자, 이제 μ „λΆ€ λͺ¨μ•„λ΄…μ‹œλ‹€. μ‹€μ œ μ‚¬μš©ν•˜λŠ” μ½”λ“œλ₯Ό μ œμ™Έν•˜λ©΄ 22쀄 λ‚¨μ§“μ˜ μ½”λ“œλ‘œκ΅°μš”.

const isPromise = obj => Boolean(obj) && typeof obj.then === 'function';

const next = (iter, callback, prev = undefined) => {
  const item = iter.next(prev);
  const value = item.value;

  if (item.done) return callback(prev);

  if (isPromise(value)) {
    value.then(val => {
      setImmediate(() => next(iter, callback, val));
    });
  } else {
    setImmediate(() => next(iter, callback, value));
  }
};

const gensync = (fn) =>
    (...args) => new Promise(resolve => {
  next(fn(...args), val => resolve(val));
});



/* How to use gensync() */

const fetchSomething = () => new Promise((resolve) => {
  setTimeout(() => resolve('future value'), 500);
});

const asyncFunc = gensync(function* () {
  const result = yield fetchSomething(); // ν”„λΌλ―ΈμŠ€λ₯Ό λ°˜ν™˜

  // ν”„λΌλ―ΈμŠ€λ₯Ό κΈ°λ‹€λ Έλ‹€κ°€, ν”„λΌλ―ΈμŠ€μ˜ ν•΄κ²°λœ κ²°κ³Όλ₯Ό μ‚¬μš©
  yield result + ' 2';
});

// asyncFuncλ₯Ό ν˜ΈμΆœν•˜λ©΄μ„œ 인자λ₯Ό μ „λ‹¬ν•œλ‹€.
asyncFunc('param1', 'param2', 'param3')
  .then(val => console.log(val)); // 'future value 2'

이 기법을 μ‹€μ œ μ½”λ“œμ— μ μš©ν•˜κ³  μ‹Άλ‹€λ©΄, λ‹Ήμ—°νžˆ Co.jsλ₯Ό λŒ€μ‹  μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€. 이 λΌμ΄λΈŒλŸ¬λ¦¬μ—λŠ” 였λ₯˜ 처리 둜직이 μ μš©λ˜μ–΄ 있고(μœ„μ˜ μ˜ˆμ‹œμ—μ„œλŠ” μ½”λ“œκ°€ κΈΈμ–΄μ§€λŠ” 것을 λ°©μ§€ν•˜κ³ μž μ œμ™Έν–ˆμŠ΅λ‹ˆλ‹€), 배포 μˆ˜μ€€μ˜ ν…ŒμŠ€νŠΈκ°€ μ™„λ£Œλ˜μ—ˆμœΌλ©°, κ·Έ 외에 쒋은 κΈ°λŠ₯듀이 ν¬ν•¨λ˜μ–΄μžˆμŠ΅λ‹ˆλ‹€.

ν”„λΌλ―ΈμŠ€μ—μ„œ μ˜΅μ €λ²„λΈ”λ‘œ

μœ„μ˜ μ˜ˆμ‹œλŠ” μ•„μ£Ό ν₯λ―Έλ‘­κ³ , Co.jsλŠ” λΆ„λͺ… 비동기 흐름 μ œμ–΄λ₯Ό λ‹¨μˆœν™”ν•΄μ£ΌλŠ” 쒋은 λΌμ΄λΈŒλŸ¬λ¦¬μž…λ‹ˆλ‹€. λ‹€λ§Œ, ν•œκ°€μ§€ λ¬Έμ œκ°€ μžˆμŠ΅λ‹ˆλ‹€. λ°˜ν™˜κ°’μ΄ ν”„λΌλ―ΈμŠ€λΌλŠ” κ²ƒμž…λ‹ˆλ‹€. λ‹€λ“€ μ•„μ‹œλ‹€μ‹œν”Ό, ν”„λΌλ―ΈμŠ€λŠ” ν•˜λ‚˜μ˜ λ°˜ν™˜κ°’ λ˜λŠ” 거절(rejection)λ§Œμ„ λ§Œλ“€μ–΄λ‚Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

μ œλ„ˆλ ˆμ΄ν„°λŠ” μ‹œκ°„μ΄ 지남에 따라 λ§Žμ€ 값듀을 λ§Œλ“€μ–΄λ‚Ό 수 μžˆμŠ΅λ‹ˆλ‹€. μ‹œκ°„μ΄ 지남에 따라 λ§Žμ€ 값듀을 λ§Œλ“€μ–΄λ‚Ό 수 μžˆλŠ” 것, μš°λ¦¬κ°€ μ•Œκ³  μžˆλŠ” λ‹€λ₯Έ 게 또 μžˆλ‚˜μš”? λ°”λ‘œ μ˜΅μ €λ²„λΈ”(Observable)μž…λ‹ˆλ‹€. ν”Όλ³΄λ‚˜μΉ˜ μ œλ„ˆλ ˆμ΄ν„°λ₯Ό μžλ°”μŠ€ν¬λ¦½νŠΈλ‘œ μž‘μ„±ν•˜λ©° 깨달은 λ†€λΌμš΄ 점 7가지λ₯Ό λ‹€μ‹œ λ– μ˜¬λ €λ³΄μ„Έμš”:

μ²˜μŒμ—λŠ” μ œλ„ˆλ ˆμ΄ν„°μ— λŒ€ν•œ 것이 ꡉμž₯히 λ§ˆμŒμ— λ“€μ—ˆμ§€λ§Œ, 쑰금 μ‚¬μš©ν•΄λ³΄κ³  λ‚˜λ‹ˆ, μ‹€μ œ 제 μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜ μ½”λ“œ μƒμ—μ„œ μ œλ„ˆλ ˆμ΄ν„°λ₯Ό 잘 ν™œμš©ν•œ μ‚¬μš©λ‘€(use-case)λ₯Ό μ°ΎκΈ° νž˜λ“€μ—ˆμŠ΅λ‹ˆλ‹€. μ œλ„ˆλ ˆμ΄ν„°κ°€ ν•„μš”ν•œ λŒ€λΆ€λΆ„μ˜ 경우라면 μ €λŠ” 였히렀 RxJSλ₯Ό μ‚¬μš©ν–ˆμŠ΅λ‹ˆλ‹€. APIκ°€ 훨씬 ν’λΆ€ν•˜κΈ° λ•Œλ¬Έμ΄μ£ .

ν”„λΌλ―ΈμŠ€λŠ” (μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜μ™€ 달리) 단 ν•˜λ‚˜μ˜ κ°’λ§Œ λ§Œλ“€μ–΄λ‚Ό 수 있고, μ˜΅μ €λ²„λΈ”μ€ (μ œλ„ˆλ ˆμ΄ν„° ν•¨μˆ˜μ²˜λŸΌ) κ³„μ†ν•΄μ„œ λ§Žμ€ 값듀을 λ§Œλ“€μ–΄λ‚Ό 수 있기 λ•Œλ¬Έμ—, async ν•¨μˆ˜λ₯Ό λ‹€λ£¨λŠ” λ°μ—λŠ” μ˜΅μ €λ²„λΈ”μ˜ APIκ°€ ν”„λΌλ―ΈμŠ€λ³΄λ‹€ 더 μ ν•©ν•˜λ‹€λŠ” 것이 개인적인 μƒκ°μž…λ‹ˆλ‹€.

μ˜΅μ €λ²„λΈ”μ΄λž€ λ¬΄μ—‡μΈκ°€μš”?

SingularPlural
SpatialValueIterable
TemporalPromiseObservable

μœ„μ˜ ν‘œλŠ” Kris Kowal이 μž‘μ„±ν•œ κΈ€ GTOR: A General Theory of Reactivityμ—μ„œ κ°€μ Έμ™”μŠ΅λ‹ˆλ‹€. λŒ€μƒμ— λŒ€ν•˜μ—¬ μ‹œκ°„κ³Ό 곡간을 κΈ°μ€€μœΌλ‘œ λΆ„ν•΄ν•©λ‹ˆλ‹€. λ™κΈ°μ μœΌλ‘œ κ°€μ Έμ˜¬ 수 μžˆλŠ” 값은 곡간을 μ†ŒλΉ„ν•˜μ§€λ§Œ(λ©”λͺ¨λ¦¬ μƒμ˜ κ°’), μ‹œκ°„μœΌλ‘œλΆ€ν„° λΆ„λ¦¬λ˜μ–΄μžˆμŠ΅λ‹ˆλ‹€. μ΄λŸ¬ν•œ 값이 Pull API μž…λ‹ˆλ‹€.

미래의 νŠΉμ • μ΄λ²€νŠΈμ— μ˜μ‘΄ν•˜λŠ” 값은 λ™κΈ°μ μœΌλ‘œ μ‚¬μš©λ  수 μ—†μŠ΅λ‹ˆλ‹€. μ‚¬μš©ν•  수 있게 되기 μ „κΉŒμ§€ ν•΄λ‹Ή 값이 처리되기λ₯Ό κΈ°λ‹€λ €μ•Ό ν•©λ‹ˆλ‹€. μ΄λŸ¬ν•œ 값은 Push API 둜, μ–Έμ œλ‚˜ νŠΉμ • ν˜•νƒœμ˜ ꡬ독 λ˜λŠ” μ•Œλ¦Ό λ©”μ»€λ‹ˆμ¦˜μ„ κ°€μ§€κ²Œ λ©λ‹ˆλ‹€. μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œλŠ”, 콜백 ν•¨μˆ˜ ν˜•νƒœλ₯Ό λ³΄μ΄λŠ” 것이 λ³΄ν†΅μž…λ‹ˆλ‹€.

미래의 값을 λ‹€λ£° λ•Œμ—λŠ”, 값이 μ‚¬μš© κ°€λŠ₯ν•΄μ§ˆ λ•Œμ— μ•Œλ¦Όμ„ λ°›λŠ” 것이 ν•„μš”ν•©λ‹ˆλ‹€. 그것이 Push μž…λ‹ˆλ‹€.

ν”„λΌλ―ΈμŠ€λž€, μ–΄λ–€ ν”„λΌλ―ΈμŠ€κ°€ ν•΄κ²°λ˜μ—ˆκ±°λ‚˜ κ±°μ ˆλ˜λ©΄μ„œ 단일 결과값을 λ°˜ν™˜ν•  λ•Œμ—, 미리 정해진 일련의 ν”„λ‘œκ·Έλž¨ μ½”λ“œλ₯Ό ν˜ΈμΆœν•˜λŠ” Push λ©”μ»€λ‹ˆμ¦˜μž…λ‹ˆλ‹€.

μ˜΅μ €λ²„λΈ”μ€ ν”„λΌλ―ΈμŠ€μ™€ λΉ„μŠ·ν•˜μ§€λ§Œ, μ˜΅μ €λ²„λΈ”μ€ μƒˆλ‘œμš΄ 값이 μ‚¬μš©κ°€λŠ₯ν•΄μ§ˆ λ•Œλ§ˆλ‹€ 항상 미리 정해진 일련의 ν”„λ‘œκ·Έλž¨ μ½”λ“œλ₯Ό ν˜ΈμΆœν•˜λ©°, μ‹œκ°„μ΄ 지남에 따라 λ§Žμ€ 값듀을 λ§Œλ“€μ–΄λ‚Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

μ˜΅μ €λ²„λΈ”μ˜ μ£Όμš” κΈ°λŠ₯은 .subscribe() λ©”μ„œλ“œλ‘œ, μ„Έ 가지 μ½œλ°±λ“€μ„ 인자둜 μ „λ‹¬ν•©λ‹ˆλ‹€:

  • onNext: μ˜΅μ €λ²„λΈ”μ΄ 값을 λ§Œλ“€μ–΄λ‚Ό λ•Œλ§ˆλ‹€ ν˜ΈμΆœλ©λ‹ˆλ‹€.
  • onError: μ˜΅μ €λ²„λΈ”μ΄ 값을 λ§Œλ“€μ–΄λ‚΄λŠ” κ³Όμ •μ—μ„œ 였λ₯˜κ°€ λ°œμƒν•˜κ±°λ‚˜ μ‹€νŒ¨ν–ˆμ„ λ•Œμ— ν˜ΈμΆœλ©λ‹ˆλ‹€.
  • onCompleted: κ°€μž₯ λ§ˆμ§€λ§‰μœΌλ‘œ onNextκ°€ ν˜ΈμΆœλ˜μ—ˆμ„ λ•Œμ— λ’€μ΄μ–΄μ„œ ν˜ΈμΆœλ©λ‹ˆλ‹€. 단, 였λ₯˜κ°€ λ°œμƒν•œ 적이 μ—†μ–΄μ•Ό ν•©λ‹ˆλ‹€.

λ”°λΌμ„œ, 동기적인 μŠ€νƒ€μΌμ˜ async ν•¨μˆ˜λ₯Ό μœ„ν•œ μ˜΅μ €λ²„λΈ” APIλ₯Ό κ΅¬ν˜„ν•˜κ³  μ‹Άλ‹€λ©΄, μœ„μ˜ μ„Έ μΈμžλ“€μ„ 전달해야 ν•©λ‹ˆλ‹€. λ°”λ‘œ λ§Œλ“€μ–΄λ³΄λ„λ‘ ν•©μ‹œλ‹€. μ•„, onErrorλŠ” μž μ‹œ λ’€λ‘œ 미뀄두죠.

const isPromise = obj => Boolean(obj) && typeof obj.then === 'function';

const next = (iter, callbacks, prev = undefined) => {
  const { onNext, onCompleted } = callbacks;
  const item = iter.next(prev);
  const value = item.value;

  if (item.done) {
    return onCompleted();
  }

  if (isPromise(value)) {
    value.then(val => {
      onNext(val);
      setImmediate(() => next(iter, callbacks , val));
    });
  } else {
    onNext(value);
    setImmediate(() => next(iter, callbacks, value));
  }
};

const gensync = (fn) => (...args) => ({
  subscribe: (onNext, onError, onCompleted) => {
    next(fn(...args), { onNext, onError, onCompleted });
  }
});


/* How to use gensync() */

const fetchSomething = () => new Promise((resolve) => {
  setTimeout(() => resolve('future value'), 500);
});

const myFunc = function* (param1, param2, param3) {
  const result = yield fetchSomething(); // ν”„λΌλ―ΈμŠ€λ₯Ό λ°˜ν™˜

  // ν”„λΌλ―ΈμŠ€λ₯Ό κΈ°λ‹€λ Έλ‹€κ°€, ν”„λΌλ―ΈμŠ€μ˜ ν•΄κ²°λœ κ²°κ³Όλ₯Ό μ‚¬μš©
  yield result + ' 2';
  yield param1;
  yield param2;
  yield param3;
}

const onNext = val => console.log(val);
const onError = err => console.log(err);
const onCompleted = () => console.log('done.');

const asyncFunc = gensync(myFunc);

// asyncFuncλ₯Ό ν˜ΈμΆœν•˜λ©΄μ„œ 인자λ₯Ό μ „λ‹¬ν•œλ‹€.
asyncFunc('a param', 'another param', 'more params!')
  .subscribe(onNext, onError, onCompleted);
// future value
// future value 2
// a param
// another param
// more params!
// done.

이 μ΅œμ’… λ²„μ „μ˜ μ½”λ“œκ°€ κ°€μž₯ λ§ˆμŒμ— λ“œλŠ”κ΅°μš”. κΈ°λŠ₯이 더 λ‹€μ–‘ν•΄μ‘ŒκΈ° λ•Œλ¬Έμ΄μ£ . 사싀, λ„ˆλ¬΄ λ§ˆμŒμ— λ“€μ–΄μ„œ, 였λ₯˜ 처리 κΈ°λŠ₯을 λ”ν•œ λ’€ Ogen이라고 이름을 λΆ™μ˜€μŠ΅λ‹ˆλ‹€. 무엇보닀도, μ§„μ •ν•œ Rx Obervable κ°μ²΄λ‹€μš΄ κΈ°λŠ₯듀을 μΆ”κ°€ν–ˆμŠ΅λ‹ˆλ‹€. λ‚΄λΆ€ μš”μ†Œλ“€μ— λŒ€ν•˜μ—¬ .map(), .filter(), .skip() 등을 λΉ„λ‘―ν•œ λ‹€μ–‘ν•œ κΈ°λŠ₯듀을 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Ogen의 κΈ°λŠ₯이 κΆκΈˆν•˜λ©΄ Githubλ₯Ό ν™•μΈν•΄μ£Όμ„Έμš”.

비동기 흐름 μ œμ–΄λ₯Ό ν–₯μƒμ‹œμΌœμ£ΌλŠ” λ‹€μ–‘ν•œ μ˜΅μ €λ²„λΈ” λΌμ΄λΈŒλŸ¬λ¦¬λ“€μ΄ μ‘΄μž¬ν•©λ‹ˆλ‹€. μ œκ°€ μ œλ„ˆλ ˆμ΄ν„°λ₯Ό μ‚¬μš©ν•΄μ˜€μ§€ μ•Šμ•˜λ˜ κ°€μž₯ 큰 μ΄μœ μ΄κΈ°λ„ ν•©λ‹ˆλ‹€λ§Œ, μ΄μ œλŠ” 동기적인 μŠ€νƒ€μΌμ˜ μ½”λ“œμ™€ μ˜΅μ €λ²„λΈ”μ„ Ogen을 μ‚¬μš©ν•˜μ—¬ μ μ ˆν•˜κ²Œ ν˜Όμš©ν•  λ“― ν•˜κ΅°μš”. μ–΄μ©Œλ©΄, μ œλ„ˆλ ˆμ΄ν„°λ₯Ό 점점 더 μ‚¬μš©ν•˜κ²Œ 될 지도 λͺ¨λ₯΄κ² μŠ΅λ‹ˆλ‹€.

profile
😊 / https://duge.space/cadenzahFHR4yZ

0개의 λŒ“κΈ€