ES9: Async Iteration

logo


배경지식 🚩

  • μ΄ν„°λŸ¬λΈ”(Iterable)은 [Symbol.iterator]() λ©”μ†Œλ“œκ°€ κ΅¬ν˜„λœ 객체λ₯Ό λ§ν•˜λ©°, 이 λ©”μ†Œλ“œλŠ” μ΄ν„°λ ˆμ΄ν„°(Iterator)λ₯Ό λ°˜ν™˜ν•œλ‹€.
  • μ΄ν„°λ ˆμ΄ν„°(Iterator)λŠ” { value, done } 쌍의 μ΄ν„°λ ˆμ΄ν„° κ²°κ³Ό 객체(Iterater Result Object)λ₯Ό λ°˜ν™˜ν•˜λŠ” nextλ©”μ†Œλ“œκ°€ κ΅¬ν˜„λ˜μ–΄μžˆλ‹€.
  • μ œλ„€λ ˆμ΄ν„° ν•¨μˆ˜(Generator functions)λŠ” ν˜ΈμΆœμ‹œμ μ—λŠ” μ½”λ“œλΈ”λ‘μ΄ μ‹€ν–‰λ˜μ§€ μ•ŠμœΌλ©° μ œλ„€λ ˆμ΄ν„°(Genrator) 객체λ₯Ό λ°˜ν™˜ν•œλ‹€. μ œλ„€λ ˆμ΄ν„° κ°μ²΄λŠ” μ΄ν„°λŸ¬λΈ”κ³Ό μ΄ν„°λ ˆμ΄ν„° μΈν„°νŽ˜μ΄μŠ€λ₯Ό λͺ¨λ‘ μ€€μˆ˜ν•œλ‹€.

κ°œμš”μ™€ 동기

μ΄ν„°λ ˆμ΄ν„°(Iterator) μΈν„°νŽ˜μ΄μŠ€(ECMAScript 2015μ—μ„œ μ†Œκ°œλ¨)λŠ” 순차적으둜 데이터λ₯Ό μ ‘κ·Όν•˜λŠ” ν”„λ‘œν† μ½œμ΄λ‹€. 기본적인 μΈν„°νŽ˜μ΄μŠ€λŠ” { value, done } νŠœν”Œμ„ λ°˜ν™˜ν•˜λŠ” next() λ©”μ†Œλ“œκ°€ 이며, done은 μ΄ν„°λ ˆμ΄ν„°κ°€ 끝에 λ„λ‹¬ν–ˆλŠ”μ§€ μ—¬λΆ€λ₯Ό λ‚˜νƒ€λ‚΄λŠ” boolean, valueλŠ” 수차적으둜 μ‚°μΆœλ˜λŠ” 값을 λ‚˜νƒ€λ‚Έλ‹€.


μ΄ν„°λ ˆμ΄ν„°λŠ” 동기식(synchronous) 데이터 μ†ŒμŠ€λ₯Ό λ‚˜νƒ€λ‚΄λŠ”λ°μ— μ ν•©ν•˜λ‹€. μžλ°”μŠ€ν¬λ¦½νŠΈ κ°œλ°œμžκ°€ 마주치게 λ˜λŠ” λ§Žμ€ 데이터 μ†ŒμŠ€κ°€ λ™κΈ°μ‹μ΄μ§€λ§Œ(예λ₯Όλ“€μ–΄ λ©”λͺ¨λ¦¬μƒμ˜ λ°°μ—΄μ΄λ‚˜ μžλ£Œκ΅¬μ‘°μ™€ 같은) 그렇지 μ•Šμ€ 것듀도 λ§Žλ‹€. 예λ₯Όλ“€μ–΄ I/O 접근이 ν•„μš”ν•œ λͺ¨λ“  데이터 μ†ŒμŠ€λŠ” 일반적으둜 이벀트 κΈ°λ°˜μ΄κ±°λ‚˜ 슀트리밍 비동기 APIλ₯Ό μ‚¬μš©ν•œλ‹€. λΆˆν–‰νžˆλ„, μ΄ν„°λ ˆμ΄ν„°λŠ” κ·ΈλŸ¬ν•œ 데이터 μ†ŒμŠ€λ₯Ό λ‚˜νƒ€λ‚΄λŠ”λ° μ‚¬μš©ν•  수 μ—†λ‹€.


(ν”„λ‘œλ―ΈμŠ€μ˜ λ°˜λ³΅μžλ„ μΆ©λΆ„ν•˜μ§€ μ•Šλ‹€. κ·Έ μ΄μœ λŠ” 비동기 κ²°μ •λ§Œ ν—ˆμš©ν•  뿐 μ•„λ‹ˆλΌ, done μƒνƒœμ˜ 동기 κ²°μ •κΉŒμ§€λ„ μš”κ΅¬ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.)


비동기 데이터 μ†ŒμŠ€μ— λŒ€ν•œ 데이터 μ ‘κ·Ό ν”„λ‘œν† μ½œμ„ μ œκ³΅ν•˜κΈ° μœ„ν•΄ AyncsIterator μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ†Œκ°œν•œλ‹€. 비동기 μ΄ν„°λ ˆμ΄μ…˜μ„ μ§€μ›ν•˜λŠ” for-await-of와 비동기 μ œλ„€λ ˆμ΄ν„° ν•¨μˆ˜μ΄λ‹€.πŸŽ‰


비동기 μ΄ν„°λ ˆμ΄ν„°μ™€ 비동기 μ΄ν„°λŸ¬λΈ”(Async iterators and async iterables)

비동기 μ΄ν„°λ ˆμ΄ν„°(Async Iterator)λŠ” next() λ©”μ†Œλ“œκ°€ { value, done } μŒμ„ μœ„ν•œ ν”„λ‘œλ―ΈμŠ€(Promise)λ₯Ό λ°˜ν™˜ν•œλ‹€λŠ” 것을 μ œμ™Έν•˜λ©΄ 기쑴의 동기식 μ΄ν„°λ ˆμ΄ν„°μ™€ μœ μ‚¬ν•˜λ‹€. μœ„μ—μ„œ μ–ΈκΈ‰ν–ˆλ“―μ΄ μ΄ν„°λ ˆμ΄ν„°μ˜ λ‹€μŒ κ°’κ³Ό done μƒνƒœλ₯Ό λͺ¨λ‘ μ•Œ 수 μ—†κΈ° λ•Œλ¬Έμ— μ΄ν„°λ ˆμ΄ν„° κ²°κ³Ό μŒμ— λŒ€ν•œ ν”„λ‘œλ―ΈμŠ€λ₯Ό λ°˜ν™˜ν•΄μ•Ό ν•œλ‹€.

const { value, done } = syncIterator.next();
asyncIterator.next().then(({ value, done }) => /* ... */);

λ˜ν•œ 비동기 μ΄ν„°λ ˆμ΄ν„°λ₯Ό ν†΅μš©λ˜κΈ° μœ„ν•˜μ—¬ μ‚¬μš©λ  μƒˆλ‘œμš΄ 심볼(symbol)인 Symbol.asyncIteratorλ₯Ό μ†Œκ°œν•œλ‹€. 이것은 Symbol.iteratorκ°€ 일반 동기식 μ΄ν„°λŸ¬λΈ”μΈ 것을 μ•Œλ €μ€¬λ˜ 것과 λ§ˆμ°¬κ°€μ§€λ‘œ, μž„μ˜μ˜ 객체가 비동기 μ΄ν„°λŸ¬λΈ”μž„μ„ μ•Œλ €μ€€λ‹€. 이것을 μ‚¬μš©ν•  수 μžˆλŠ” 클래슀의 μ˜ˆλŠ” μ•„λ§ˆλ„ 읽을 수 μžˆλŠ” 슀트림(readable stream)이 될것이닀.


비동기 μ΄ν„°λ ˆμ΄ν„°μ˜ λ‚΄λΆ€ μ„€κ³„μ—λŠ” μš”μ²­ 큐의 섀계가 ν¬ν•¨λ˜μ–΄ μžˆλ‹€. 이전 μš”μ²­μ˜ κ²°κ³Όκ°€ ν•΄κ²°λ˜κΈ° 전에 μ΄ν„°λ ˆμ΄ν„° λ©”μ†Œλ“œκ°€ μ—¬λŸ¬ 번 호좜 될 수 μžˆμœΌλ―€λ‘œ, λͺ¨λ“  이전 μš”μ²­ μ‘°μž‘μ΄ μ™„λ£Œ 될 λ•ŒκΉŒμ§€ 각 λ©”μ†Œλ“œ ν˜ΈμΆœμ„ λ‚΄λΆ€μ μœΌλ‘œ 큐에 λ„£μ–΄μ•Όν•œλ‹€.


비동기 반볡문: for-await-of

비동기 μ΄ν„°λŸ¬λΈ” 객체λ₯Ό λ°˜λ³΅ν•˜λŠ” for-of 반볡문의 λ³€ν˜•μ„ μ†Œκ°œν•œλ‹€. μ‚¬μš© μ˜ˆλŠ” λ‹€μŒκ³Ό κ°™λ‹€:

for await (const line of readLines(filePath)) {
  console.log(line);
}

비동기 for-of 문은 비동기 ν•¨μˆ˜, 비동기 μ œλ„€λ ˆμ΄ν„° ν•¨μˆ˜(뒀에 보게될)와 ν•¨κ»˜ μ‚¬μš© κ°€λŠ₯ν•˜λ‹€. 루프λ₯Ό μ‹€ν–‰ν•˜λŠ”λ™μ•ˆ [Symbol.asyncIterator]()λ©”μ†Œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ λ°μ΄ν„°μ†ŒμŠ€μ—μ„œ 비동기 μ΄ν„°λ ˆμ΄ν„°κ°€ μƒμ„±λœλ‹€. 맀번 λ‹€μŒκ°’μ— μ ‘κ·Όν• λ•Œλ§ˆλ‹€ μ΄ν„°λ ˆμ΄ν„° λ©”μ„œλ“œλ₯Ό 톡해 λ°˜ν™˜λœ ν”„λ‘œλ―ΈμŠ€(Promise)에 awaitκ°€ μ•”μ‹œμ μœΌλ‘œ μ μš©λœλ‹€.


비동기 μ œλ„€λ ˆμ΄ν„° ν•¨μˆ˜(Async generator functions)

비동기 μ œλ„€λ ˆμ΄ν„° ν•¨μˆ˜λŠ” μ œλ„€λ ˆμ΄ν„°μ˜ κΈ°λŠ₯κ³Ό μœ μ‚¬ν•˜μ§€λ§Œ λ‹€μŒκ³Ό 같은 차이점이 μžˆλ‹€:

  • ν˜ΈμΆœμ‹œμ— 직접 { value, done }을 λ°˜ν™˜ν•˜λŠ”κ²ƒ λŒ€μ‹ μ—, next, throw, return λ©”μ†Œλ“œλ₯Ό 가진 비동기 μ œλ„€λ ˆμ΄ν„° 객체λ₯Ό λ°˜ν™˜ν•˜λ©°, return(Promise)을 ν†΅ν•΄μ„œ { value, done }을 λ°˜ν™˜ν•œλ‹€.
  • await 와 for-await-ofλ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.
  • yield*의 λ™μž‘μ€ 비동기 μ΄ν„°λŸ¬λΈ”(iterables)에 μœ„μž„μ„ μ§€μ›ν•˜λ„λ‘ μˆ˜μ •λ˜μ—ˆλ‹€.

예λ₯Ό λ“€λ©΄ λ‹€μŒκ³Ό κ°™λ‹€.

async function* readLines(path){
  let file = await fileOpen(path);
  
  try{
    while(!file.EOF){
      yield await file.readLine();
    }
  } finally {
    await file.close();
  }
}

이 ν•¨μˆ˜λŠ” 비동기 μ œλ„€λ ˆμ΄ν„° 객체λ₯Ό λ°˜ν™˜ν•˜λ©°, 이전 예제의 for-await-ofλ¬Έκ³Ό ν•¨κ»˜ μ‚¬μš© κ°€λŠ₯ν•˜λ‹€.


μ˜ˆμ‹œ

const getRandomTime = (() => {
  const randomTime = [100,200,300,400];
  const length = randomTime.length;
  let index = 0;
  return () => {
    index = (index + 1 + length)%length;
    return randomTime[index];
  }
})();

const login = async ({ id, password }) => {
  return await new Promise(resolve => {
    const wait = setTimeout(() => {
      clearTimeout(wait);
      resolve(`${id}계정 둜그인 성곡`);
    }, 1000);
  });
}

// users/:id
const parseProfile = async userId => {
  const someParsingLogic = () => 
    new Promise(resolve => {
      const src = 'http://someurl.com';
      const wait = setTimeout(() => {
        clearTimeout(wait);
        resolve({
          userId,
          name:`철수${userId}`,
          src,
        });
      }, getRandomTime());
    });

  return await someParsingLogic();
}

async function* parseProfileAll(formData, range){
  yield await login(formData);
	let index = 0;
	while(index++ < range){
		yield await parseProfile(index);
	}
}

(async () =>{
  for await (const n of parseProfileAll('hacker', '123123', 10)) {
    console.log(n); // 맀번 λ£¨ν”„λ§ˆλ‹€ [Symbol.asyncIterator]()λ©”μ†Œλ“œλ₯Ό 톡해 λ°˜ν™˜λœ, Promise에 μ•”μ‹œμ μœΌλ‘œ awaitκ°€ μ μš©λœλ‹€.
  }    
})();

μ°Έκ³