JavaScript 공부 - async/await

그래도 아무튼 개발자·2023년 3월 23일
0

JavaScript

목록 보기
8/11
post-thumbnail

이번에는 async함수와 await 키워드에 대해서 알아보자

우선 그 전에 제너레이터에 대해서 간단히 짚고 넘어가야 할 듯 싶다.

제너레이터 함수

코드 블록의 실행을 일시 중지했다가 필요한 시점에 재개시킬 수 있는 함수
function* 키워드로 선언되며, 하나 이상의 yield 표현식을 포함

function* 함수이름() {
	yield 1;
}

const 함수이름 = function* () {
	yield 1;
}

뭐 요러한 방식으로 선언된다 생각하면 된다.
참고로 화살표 함수로는 정의할 수 없고, new를 통한 호출도 불가능하다.


이러한 제너레이터 함수는 yield 키워드와 next 메서드를 통해 실행 중지 및 재개를 할 수 있는데, 일반 함수는 호출 이후 제어권을 함수가 독점하게 되지만, 제너레이터는 함수 호출자에게 제어권을 양도하여 필요한 시점에 함수 실행을 재개할 수 있게 된다.

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

const generator = getFunc();

//처음 next를 호출하면 첫번째 yield까지만 실행되고 일시 중지.
console.log(generator.next());	//{value:1, done:false}
//그 이후에도 next를 호출하면 해당 번째의 yield까지만 실행되고 일시 중지됨.
console.log(generator.next());	//{value:2, done:false}
console.log(generator.next());	//{value:3, done:false}
console.log(generator.next());	//{value:undefined, done:true}	//끝까지 실행이 완료되었으므로 true 반환


또 제너레이터를 사용하면 비동기 처리를 동기 처리처럼 동작하도록 구현이 가능하다!

하지만 제너레이터의 특성상 동기 처럼 구현한다 하더라도 코드가 생각보다 장황해지고 가독성이 살짝 안좋아진다는 단점이 있다. 이를 간단하고 가독성이 좋게 만들어 줄 수 있는 async/await 문법이 ES8에서부터 도입이 되었다.

async 함수

async 키워드를 사용하여 정의하는 함수로, 언제나 프로미스를 반환한다.

await 키워드

프로미스가 settled(비동기 처리 수행)상태가 될 때가지 대기하다가 settled 상태가 되면 프로미스가 resolve한 처리 결과를 반환하는 키워드. 반드시 프로미스 앞에서 사용해야 한다.

async/await는 프로미스 기반으로 동작하는데, 비동기 처리 결과를 후속 처리할 필요 없이 마치 동기 처리인 것처럼 프로미스를 사용할 수 있기에 자주 사용된다. 동기처리 관련 코드를 async/await를 사용하여 구현한다면,

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

async function fetchTodo() {
	const url = "https://jsonplaceholder.typicode.com/todos/1";
    
    const response = await fetch(url);
    const todo = await response.json();
    console.log(todo);
    //{userID:1, id:1, title:'delectus aut autem', completed:false}
}
fetchTodo();

co 라이브러리를 사용할 필요도 없고, 프로미스의 후속 처리 없이도 같은 결과를 출력하게 된다! (제너레이터의 경우 코드 20줄 나옴...)
사용법은 함수 앞에 async를 작성하고, async 함수 내에서만 await 키워드를 사용하면 되며, await 키워드가 작성된 코드가 다 동작하고 나서 다음 코드가 동작하게 되는 원리이다.

async function 함수명() {
  await 비동기_처리_메서드_명();
}

다른 예제를 가져온다면
function fetchItems() {
  return new Promise(function(resolve, reject) {
    const items = [1,2,3];
    resolve(items)
  });
}

async function logItems() {
  const resultItems = await fetchItems();
  console.log(resultItems); // [1,2,3]
}

위 예제에서 fetchItems 함수는 프로미스 객체를 반환하는 함수로, 실행시 프로미스가 resolve 되며 결과 값은 items 배열이 나오게 된다.
아래의 LogItems를 보면 fetchItems의 결과값이 resultItems 변수에 삽입되며, 이후 콘솔에는 그대로 [1,2,3]이 출력된다. 이 단계에서 await을 사용하지 않았다면 데이터를 받아온 시점에서 콘솔 출력과 관련한 콜백 함수나, then을 이용해야 했을 텐데, 이를 await로 인해 방지한 것이다.

async와 await의 예외처리

여타 비동기 처리와 다르게 예외처리 또한 존재한다.
비동기 처리를 위한 콜백 패턴의 단점 중 가장 심각한 점으로 꼽히는 것이 에외처리(에러처리)가 곤란하다는 것인데, 비동기 함수의 콜백 함수를 호출한 것은 비동기 함수가 아니기 때문에 'try catch' 문법으로 에러를 잡아낼 수 없다.
하지만 오늘 다루는 async/await 문법은 'try catch' 사용이 가능하다! 이유는 콜백 함수를 인수로 전달받는 비동기 함수와는 달리 프로미스를 반환하는 비동기 함수이기 때문에 명시적으로 호출할 수 있어서 호출자가 명확하다.

async function logTodoTitle() {
  try {
    const user = await fetchUser();
    if (user.id === 1) {
      const todo = await fetchTodo();
      console.log(todo.title); 
    }
  } catch (error) {
    console.log(error);
  }
}

위 코드 실행 중 발생한 네트워크 통신 오류뿐만 아니라 간단한 타입 오류 등의 일반적인 오류까지도 catch로 잡아낼 수 있다! 발견된 에러는 따로 error 객체에 담기기 때문에 에러의 유형에 맞게 에러 코드를 처리해 준다면 명확한 코드를 작성하는데 수월할 것이다.


여기까지 기존 비동기 방식(프로미스, 콜백)의 단점들을 보완하고 코드의 가독성을 높여주었던 async/await 문법에 대해서 공부해보았다.

0개의 댓글