모던 자바스크립트 Deep Dive #46. 제너레이터와 async/await

epiphany·2024년 2월 20일
post-thumbnail

46.1 제너레이터란?

코드 블록의 실행을 일시 중지했다가 필요한 시점에 재개할 수 있는 특수한 함수

  1. 제너레이터 함수는 함수 호출자에게 함수 실행의 제어권 양도 가능
    • 함수 호출자가 함수 실행을 일시 중지하거나 재개 가능
  2. 제너레이터 함수는 함수 호출자와 함수의 상태를 주고받을 수 있음
  3. 제너레이터 함수를 호출하면 제너레이터 객체를 반환

46.2 제너레이터 함수의 정의

  • function* 키워드로 선언
  • 하나 이상의 yield 표현식 포함
  • 화살표 함수로 정의할 수 없음
  • *(애스터리스트)의 위치는 function 키워드와 함수 이름 사이라면 어디든지 상관 없음
  • new 연산자와 함께 생성자 함수로 호출할 수 없음

-> 그 외에는 일반 함수와 동일

function* genFunc() {yield 1;}
function * genFunc() {yield 1;}
function *genFunc() {yield 1;}
function*genFunc() {yield 1;}

new genFunc();  // TypeError: genFunc is not a constructor

46.3 제너레이터 객체

제너레이터 함수 호출시 제너레이터 객체를 생성해 반환
제너레이터 객체는 이터러블 이면서 이터레이터이다.

이터러블(iterable):
이터러블은 순회 가능한 객체를 나타냅니다. 즉, 이터러블 객체는 내부에 반복 가능한 요소들을 가지고 있고, 이를 순차적으로 접근할 수 있는 객체입니다. 이터러블은 반드시 Symbol.iterator라는 특별한 메서드를 가지고 있어야 합니다. 이 메서드는 이터레이터를 반환해야 합니다.

이터레이터(iterator):
이터레이터는 이터러블 객체를 순회하면서 각 요소에 접근하는 역할을 합니다. 이터레이터는 next() 메서드를 가지고 있으며, 이 메서드를 호출할 때마다 순차적으로 다음 요소에 대한 정보를 반환합니다.

function* genFunc() {yield 1;}

const generator = genFunc();

console.log(Symbol.iterator in generator);  // ture
console.log('next' in generator);  // true

46.4 제너레이터의 일시 중지와 재개

제너레이터는 yield 키워드와 next 메서드를 통해 실행을 중지 및 재개할 수 있음
next 메서드를 호출하면 함수의 yield 표현식까지만 실행되고 일시 중지됨 -> 이때 함수의 제어권이 호출자로 양도됨
next 메서드를 호출하면 일시 중지된 코드부터 실행을 재개함

function* genFunc() {
  yield 1;
  yield 2;
  yield 3;
}

const generator = genFunc();

console.log(generator.next());  // {value: 1, done: false}
console.log(generator.next());  // {value: 2, done: false}
console.log(generator.next());  // {value: 3, done: false}
console.log(generator.next());  // {value: undefined, done: true}

제너레이터 객체의 next 메서드는 인수 전달 가능

  • next 메서드에 전달한 인수는 제너레이터 함수의 yield 표현식을 할당받는 변수에 할당
function* genFunc() {
  const x = yield 1;
  const y = yield (x + 10);
  return x + y;
}

const generator = genFunc(0);

let res = generator.next();  // 1은 반환되지만 x에 1이 할당되지는 않음
console.log(res); // {value: 1, done: false}

res = generator.next(10);  // x에 10이 할당되고, y에는 값이 할당되지 않고, 20이 반환됨
console.log(res); // {value: 20, done: false}

res = generator.next(20);  // y에 20이 할당되고 x+y값으로 30이 반환됨
console.log(res); // {value: 30, done: true}

46.6 async/await

async/await: 제너레이터보다 간단하고 가독성 좋게 비동기 처리를 동기 처리처럼 동작하도록 구현 가능

  • 프로미스를 기반으로 동작

    프로미스(Promise): 비동기 작업을 처리하고 조작하기 위한 객체
    프로미스는 .then() 메서드를 통해 이행 상태에 대한 처리를 등록하고, .catch() 메서드를 통해 거부 상태에 대한 처리를 등록합니다. 또한, .finally() 메서드를 사용하여 이행 또는 거부 여부와 관계없이 항상 실행되는 코드를 추가할 수 있습니다.

프로미스는 여러 개의 프로미스를 조합하고 순차적 또는 병렬로 실행하는 등의 작업도 수행할 수 있어, 비동기 코드를 더 효율적으로 관리할 수 있게 도와줍니다.

46.6.1 async 함수

await 키워드는 반드시 async 함수 내부에서 사용
async 함수는 항상 프로미스를 반환

async function foo(n) { return n; }
foo(1).then(v => console.log(v));  // 1

클래스의 constructor 메서드는 인스턴스를 반환해야하지만 async 함수는 프로미스를 반환하기 때문에 constructor 메서드는 async 메서드가 될 수 없음

class MyClass {
  async constructor() { }
  // SyntaxError: Class constructor may not be an async method
}

46.6.2 await 함수

프로미스가 settled 상태(비동기 처리가 수행된 상태)가 될 때까지 대기하다가 settled 상태가 되면 프로미스가 resolve한 처리 결과를 반환

  • await 키워드는 반드시 프로미스 앞에 사용

(1) fetch 함수가 반환한 프로미스가 settled 상태가 될때까지 (1)은 대기
프로미스가 settled 상태가 되면 프로미스가 resolve한 처리 결과가 res 변수에 할당

const fetch = require('node-fetch');

const getGithubUserName = async id => {
  const res = await fetch(`https://api.gihub.com/users/${id}`); // (1)
  ...
};

-> await 키워드는 다음 실행을 일시 중지시켰다가 프로미스가 settled 상태가 되면 재개함

46.6.3 에러 처리

async/await에서 try...catch 문을 사용하여 에러 처리 가능

  • async 함수 내에서 catch 문을 사용하지 않으면 async 함수는 발생한 에러를 reject 하는 프로미스를 반환함
const fetch = require('node-fetch');

const foo = async() => {
  const wrongUrl = 'https://wrong.url';
  
  const response = await fetch(wrongUrl);
  const data = await response.json();
  return data;
}

foo().then(console.log).catch(console.error);  // TypeError: Failed to fetch
profile
iamda.tistory.com 이사 중

0개의 댓글