2 minute read

async / await

기존의 비동기 처리방식인 콜백함수와 프로미스 문법에서 나온 단점들을 보완하고자 ES8에서는 간단하고 가독성 좋게 비동기 처리를 동기처럼 동작하도록 구현할 수 있는데 asycn / await가 도입됐다.

asycn / await는 프로미스를 기반으로 동작한다. asycn / await를 사용하면 프로미스의 then/catch/finally 후속처리 메서드에 콜백 함수를 전달해서 비동기 처리 결과를 후속 처리할 필요 없이 마치 동기처럼 프로미스를 사용할 수 있다.

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();

async & await 기본 문법

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

먼저 함수 앞에 async라는 예약어를 붙이고 함수 내부 로직 중 HTTP 통신을 하는 비동기 처리 코드 앞에 await를 붙인다. 여기서 주의해야 할 점은 비동기 처리 메서드가 반드시 프로미스 객체를 반환해야 await가 의도한 대로 동작한다.

async 함수는 async 키워드를 사용해 정의하며 언제나 프로미스를 반환한다. 만약 명시적으로 프로미스를 반환하지 않더라도 암묵적으로 반환값을 resolve하는 프로미스를 반환한다.

// async 함수 선언문
async function foo(n) { return n; }
foo(1).then(v => console.log(v)); // 1

// async 함수 표현식
const bar = async function (n) { return n; };
bar(2).then(v => console.log(v)); // 2

// async 화살표 함수
const baz = async n => n;
baz(3).then(v => console.log(v)); // 3

// async 메서드
const obj = {
  async foo(n) { return n; }
};
obj.foo(4).then(v => console.log(v)); // 4

// async 클래스 메서드
class MyClass {
  async bar(n) { return n; }
}
const myClass = new MyClass();
myClass.bar(5).then(v => console.log(v)); // 5

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

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

const getGithubUserName = async id => {
  const res = await fetch(`https://api.github.com/users/${id}`); // ①
  const { name } = await res.json(); // ②
  console.log(name); // Ungmo Lee
};

getGithubUserName('ungmo2');

await키워드는 프로미스가 settled상태가 될 때까지 대기한다. 따라서 fetch함수가 수행한 HTTP 요청에 대한 서버의 응답이 도착해서 fetch함수가 반환한 프로미스가 settled상태가 될 때까지 ①은 대기하게 된다. 이후 프로미스가 settled상태가 되면 프로미스가 resolve한 처리 결과가 res변수에 할당된다.

이처럼 await키워드는 다음 실행을 일시 중지시켰다가 프로미스가 settled상태가 되면 다시 재개한다.


에러처리

비동기 처리를 위한 콜백 패턴의 단점 중 가장 심각한 것은 에러처리가 곤란하다는 것이다. async / await에서 에러처리는 try … catch문을 사용할 수 있다. 콜백 함수를 인수로 전달받는 비동기 함수와는 달리 프로미스를 반환하는 비동기 함수는 명시적으로 호출할 수 있기 때문에 호출자가 명확하다.

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

const foo = async () => {
  try {
    const wrongUrl = 'https://wrong.url';

    const response = await fetch(wrongUrl);
    const data = await response.json();
    console.log(data);
  } catch (err) {
    console.error(err); // TypeError: Failed to fetch
  }
};

foo();

위 예제의 foo함수의 catch문은 HTTP 통신에서 발생한 네트워크 에러뿐 아니라 try코드 블록 내의 모든 문에서 발생한 일반적인 에러까지 캐치할 수 있다.

async함수 내에서 catch문을 사용해서 에러처리를 하지 않으면 async함수는 발생한 에러를 reject하는 프로미스를 반환한다. 따라서 async함수를 호출하고 Promise.prototype.catch 후속 처리 메서드를 사용해 에러를 캐치할 수도 있다.

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

참고 문서

async와 await를 사용하여 비동기 프로그래밍을 쉽게 만들기

캡틴판교 블로그