Javascript | async와 await

shin6403·2020년 12월 26일
1

이 글을 읽으려면 비동기 처리 및 콜백 함수Promise에 대해 이해하여야한다. 이 부분은 나중에 정리하기로 하자.

async & await?🤔

async와 await은 자바스크립트의 비동기 처리 패턴 중 가장 최근에 나온 문법이다.
기존의 비동기처리 방식인 콜백함수Promise의 단점을 보완한 문법이고, Promise를 좀 더 편하게 사용할 수 있다.

async 함수

async 함수부터 알아보자, async는 function 앞에 위치한다.

async function foo(){
	return 100;
}

function앞에 async를 붙이면 해당 함수는 항상 Promise를 반환한다.
Promise가 아닌 값을 반환하더라도 이행 상태의 Promise(resolved promise)로 값을 감싸 이행된 Promise가 반환되도록 한다.

아래 예시의 함수를 호출하면 result가 '100'인 이행 Promise가 반환된다.

async function foo(){
	return 100;
}

foo().then(alert); //100

이는 아래와 같이 명시적으로 Promise를 반환하는것도 가능한데, 결과는 동일하다.

async function foo(){
	return Promise.resolve(100);
}

foo().then(alert); //100

async가 붙은 함수는 반드시 Promise를 반환하고, Promise가 아닌 것은 Promise로 감싸 반환한다.

await 함수

await 문법은 다음과 같다

// await은 async 함수 안에서만 동작한다.
const value = await promise

자바스크립트는 await을 만나면 Promise가 처리(settled)될 때까지 기다리고, 결과는 그 이후 반환된다.

아래 1초 후 이행(완료)되는 Promise를 예시로 사용하여 await가 어떻게 동작하는지 살펴보자.

async function foo(){
	const promise = new Promise((resolve,reject)=>{
    	setTimeout(()=>resolve('호출완료!'),1000)
    });
  	const result = await promise; // promise가 이행(완료)될 때까지 기다림(*)
  	alert(result); 
}

foo()// '호출완료!'

함수를 호출하고, 함수 본문이 실행되는 도중 위에 (*)로 표시한 부분에서 실행이 잠시 **'중단' **되었다가 Promise가 처리되면 실행이 재개된다.
그리고 이때 Promise 객체의 결과 값이 변수 result에 할당된다.

await('기다리다' 라는 뜻의 영단어 )는 말 그대로 Promise가 처리될 때까지 함수 실행을 기다리게 만든다. Promise가 처리되면 그 결과와 함께 실행이 재개된다.

awaitpromise.then보다 좀 더 세련되게 Promise의 결과 값을 얻게 해주는 문법이다. promise.then보다 가독성 좋고 쓰기도 쉽다.


⚠️ 일반 함수엔 await을 사용할 수 없다.

async 함수가 아닌데 await을 사용하면 문법 에러가 발생한다.

function foo(){
	const promise = Promise.resolve(1);
  	const result = await promise; // Syntax Error!!
}

에러 핸들링

Promise가 정상적으로 이행(완료)되면 await promisePromise 객체의 result에 저장된 값을 반환한다.
반면 Promise가 거부되면 마치 throw문을 작성한 것처럼 에러가 던져진다.

async function foo(){
	await Promise.reject(new Error('에러 발생!!'));
}

위 코드는 아래 코드와 동일하다.

async function foo(){
	throw new Error('에러 발생!!');
}

실제 상황에선 Promise가 거부 되기 전에 약간의 시간이 지체되는 경우가 있다. 이런 경우엔 await가 에러를 던지기 전에 지연이 발생한다.

await가 던진 에러는 throw가 던진 에러를 잡을 때처럼 try..catch를 사용해 잡을 수 있다.

async function foo(){
  try{
    const response = await fetch('http://유효하지 않은 주소')
  } 
  catch(err){
  alert(err) // TypeError: failed to fetch
  }
}

foo();

에러가 발생하면 제어 흐름이 catch 블록으로 넘어간다. 여러 줄의 코드를 try로 감싸는 것도 가능하다.

async function foo(){
  try{
    const response = await fetch('http://유효하지 않은 주소')
    const user = await response.json()
  } 
  catch(err){
     // fetch와 response.json에서 발행한 에러 모두를 여기서 잡는다.
  alert(err)
  }
}

foo();

try..catch가 없으면 아래 예시의 async함수 foo()를 호출해 만든 Promise가 거부 상태가 된다.
foo().catch를 추가하면 Promise를 처리할 수 있다.

async function foo(){
	const response = await fetch('http://유효하지 않은 url')
}
//foo()는 거부 상태의 Promise가 된다.
foo.catch(alert); // TypeError:failed to fetch

.catch 추가하는걸 잊으면, 처리되지 않은 Promise 에러를 발생한다.

☑️ async/awaitpromise.then/catch 비교했을때 장점

  1. async/await를 사용하면 await가 대기를 처리해주기 때문에 .then이 필요하지 않다.
  2. .catch 대신 일반 try..catch를 사용할 수 있다.

그렇기에 promise.then을 사용하는 것 보다 async/await를 사용하는 것이 편리하다.

☑️ async/await works well with Promise.all
여러개의 Promise가 모두 처리되길 기다려야 하는 상황이라면 이 Promise들을 Promise.all로 감싸고 여기에 await을 붙여 사용할 수 있다.

// 프라미스 처리 결과가 담긴 배열을 기다린다.
let results = await Promise.all([
  fetch(url1),
  fetch(url2),
  ...
]);

실패한 프라미스에서 발생한 에러는 보통 에러와 마찬가지로 Promise.all로 전파된다.
에러 때문에 생긴 예외는 try..catch로 감싸 잡을 수 있다.

정리 📝

1. function 앞에 async 키워드를 추가하면 두 가지 효과가 있다.

  • 함수는 언제나 Promise를 반환한다.
  • 함수 안에서 await를 사용할 수 있다.

2. Promise 앞에 await 키워드를 붙이면 자바스크립트는 Promise가 처리될 때까지 대기한다. 처리가 완료되면 조건에 따라 아래와 같은 동작이 이어진다.

  • 에러 발생 – 예외가 생성됨(에러가 발생한 장소에서 throw error를 호출한 것과 동일함)
  • 에러 미발생 – Promise 객체의 result 값을 반환

3 .async/await를 함께 사용하면 읽고, 쓰기 쉬운 비동기 코드를 작성할 수 있다.

async/await를 사용하면 promise.then/catch가 거의 필요 없다.
하지만 가끔 가장 바깥 스코프에서 비동기 처리가 필요할 때같이 promise.then/catch를 써야만 하는 경우가 생기기 때문에 async/await가 프라미스를 기반으로 한다는 사실을 알고 있어야 한다.
여러 작업이 있고, 이 작업들이 모두 완료될 때까지 기다리려면 Promise.all을 활용할 수 있다.

✅ 예제


1. async와 await를 사용하여 코드 변경하기

아래 코드를 .then/catch 대신 async/await를 사용해 다시 작성해보자.

function loadJson(url) {
  return fetch(url)
    .then(response => {
      if (response.status == 200) {
        return response.json();
      } else {
        throw new Error(response.status);
      }
    })
}

loadJson('no-such-user.json')
  .catch(alert); // Error: 404

해답🔥

async function loadJson(url) {  // 함수 `loadJson`은 `aync` 함수가 된다.
 const response = await fetch(url) // 함수 안의 .then을 전부 await로 바꾼다.
 if(response.status == 200){
   const json = await response.json()
 return json;
 }
 throw new Error(response.status);
}

loadJson('no-such-user.json')
  .catch(alert); // Error: 404 // loadJson에서 던져진 에러는 .catch에서 처리된다.
				//loadJson을 호출하는 코드는 async 함수 내부가 아니기 때문에 await loadJson(…)을 사용할 수 없다.
profile
생각하는대로 살지 않으면, 사는대로 생각하게 된다.

2개의 댓글

comment-user-thumbnail
2020년 12월 26일

오 나중에 쁘라미스 공부하러 여기 오겠습니다

1개의 답글