[JavaScript] 제너레이터와 async/await

SungWoo·2024년 12월 3일

자바스크립트 공부

목록 보기
39/42
post-thumbnail

제너레이터(Generator)란?

ES6에서 도입된 특별한 종류의 함수로, 코드 블록의 실행을 일시 중단했다가 필요한 시점에 재개할 수 있는 특수한 함수를 말한다.

제너레이터는 이터러블(iterable) 객체를 생성하는데 유용하며, 복잡한 반복 작업을 간단하게 처리할 수 있다.

제너레이터와 일반 함수의 차이

  1. 제너레이터 함수는 함수 호출자에게 함수 실행의 제어권을 양도할 수 있다.
  2. 제너레이터 함수는 함수 호출자와 함수의 상태를 주고받을 수 있다.
  3. 제너레이터 함수를 호출하면 제너레이터 객체를 반환한다.

제너레이터 함수의 정의

제너레이터 함수는 function* 키워드를 사용해 정의한다. 그리고 하나 이상의 yield 표현식을 포함한다. 이것을 제외하면 일반 함수를 정의하는 방법과 같다.

function* exampleGenerator() {
  try {
	  yield '첫 번째 호출';
	  yield '두 번째 호출';
	  return '마지막 호출';
	} catch (e) {
		console.error("[ERROR] :", e);
	}
}

yield 는 제너레이터 함수가 실행 상태를 중단하고 특정 값을 호출자에게 반환하게 한다.

  • 제너레이터 함수는 화살표 함수로 정의할 수 없다.
  • 제너레이터 함수는 new 연산자와 함께 생성자 함수로 호출할 수 없다.

제너레이터 객체

제너레이터 함수를 호출하면 일반 함수처럼 함수 코드 블록을 실행하는 것이 아니라 제너레이터 객체를 생성해 반환한다. 제너레이터 함수가 반환한 제너레이터 객체는 이터러블(iterable)이면서 동시에 이터레이터(iterator)다.

const gen = exampleGenerator();

console.log(gen.next()); // { value: '첫 번째 호출', done: false }
console.log(gen.next()); // { value: '두 번째 호출', done: false }
console.log(gen.throw(new Error('강제 에러'))); 
// [ERROR] : Error: 강제 에러
// { value: undefined, done: true }
console.log(gen.return('직접 종료')); 
// { value: '직접 종료', done: true }

제너레이터 객체는 next 메서드를 갖는 이터레이터이지만 이터레이터에는 없는 return , throw 메서드를 갖는다.

next()

next 메서드를 호출하면 제너레이터 함수의 yield 표현식까지 코드 블록을 실행하고 yield 된 값을 value 프로퍼티 값으로, false를 done 프로퍼티 값으로 갖는 이터레이터 리절트 객체를 반환한다.

  • value : yield 에 의해 반환된 값
  • done : 제너레이터가 끝났는지 여부를 나타내는 Boolean 값

return

return 메서드를 호출하면 인수로 전달받은 값을 value 프로퍼티 값으로, true를 done 프로퍼티 값으로 갖는 이터레이터 리절트 객체를 반환한다.

throw

throw 메서드를 호출하면 인수로 전달받은 에러를 발생시키고 undefined를 value 프로퍼티 값으로, true를 done 프로퍼티 값으로 갖는 이터레이터 리절트 객체를 반환한다.


제너레이터의 활용

  1. 이터러블의 구현 : 커스텀 이터러블을 쉽게 생성할 수 있다.
  2. 비동기 처리 : yield 를 사용해 비동기 작업을 순차적으로 처리할 수 있다.
// 비동기 데이터를 제공하는 제너레이터
async function* asyncNumberGenerator() {
  for (let i = 1; i <= 3; i++) {
    await new Promise((resolve) => setTimeout(resolve, 1000)); // 1초 대기
    yield i; // 숫자를 비동기로 반환
  }
}

// 제너레이터 데이터를 처리하는 함수
async function processNumbers() {
  const numberGenerator = asyncNumberGenerator();

  for await (const number of numberGenerator) {
    console.log('받은 숫자:', number);
  }
}

// 실행
processNumbers();

async / await

제너레이터를 사용해서 비동기 처리를 동기 처리처럼 동작하도록 구현했지만 코드가 장황해지고 가독성도 나빠졌다. ES8(ECMAScript2017)에서는 제너레이터보다 간단하고 가독성 좋게 비동기 처리를 동기 처리처럼 동작하도록 구현할 수 있는 async/await가 도입되었다.

async

async 키워드는 함수를 비동기 함수로 만들어준다. 비동기 함수는 항상 프로미스를 반환하며, 함수 내부에서 명시적으로 값을 반환하면 자동으로 Promise.resolve() 로 감싸진다.

async function fetchData() {
  return '데이터 반환';
}

fetchData().then(console.log); // 데이터 반환

await

await 키워드는 프로미스가 처리될 때(settled 상태)까지 비동기 함수의 실행을 일시 중지했다가 settled 상태가 되면 프로미스가 resolve한 처리 결과를 반환한다. awaitasync 함수 내에서만 사용할 수 있다.

async function getUserData() {
  const user = await fetch('https://api.example.com/user');
  const data = await user.json();
  return data;
}

getUserData().then(console.log);

에러 처리

async/await 를 사용할 때, 비동기 작업 중에 발생하는 에러는, try...catch 문을 사용해 처리할 수 있다. 이는 기존의 .catch() 를 사용하는 프로미스 방식보다 코드가 더 읽기 쉽고 구조적이다.

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) {
      throw new Error('데이터를 가져오는 데 실패했습니다');
    }
    const data = await response.json();
    console.log('가져온 데이터:', data);
  } catch (error) {
    console.error('에러 발생:', error.message);
  }
}

fetchData();

만약 async 함수 내에서 catch문을 사용해서 에러 처리를 하지 않으면 async 함수는 발생한 에러를 reject하는 프로미스를 반환한다.

profile
어제보다 더 나은

0개의 댓글