이전에는 비동기 처리를 위해 Promise와 콜백을 사용했지만, async/await의 등장으로 코드의 가독성과 작성 편의성이 크게 향상되었다. 그렇다면 이 async/await는 어떻게 작동하고 왜 이렇게 중요성이 강조되고 있는 것일까?
이에, 이번 글을 통해 비동기 처리에 혁신을 가져온 async/await 구문에 대해 학습해보며, 이에 대해 깊게 알아보고자 한다.
Async/Await 구문은 자바스크립트의 비동기 처리 패턴에서 큰 전환점을 마련해주었으며, Promise를 더 쉽고 직관적으로 사용할 수 있게 해주는 일종의 Syntactic sugar이다.
따라서, 이를 통해 기존의 복잡한 Promise Chaining 코드를 마치 동기적인 코드처럼 간결하고 명확하게 작성할 수 있게 해주어 코드의 간결성과 가독성을 높여주는 중요한 역할을 한다.
async는 asynchronous의 줄임말로 비동기를 의미한다. 따라서, async는 함수 안에 비동기적으로 실행될 부분이 있다는 것을 의미한다.
이 때, async함수는 항상 프로미스 객체를 리턴한다.
await은 앞서 말한 async 함수 내에서 비동기적으로 실행될 부분을 나타낸다. 즉, await이 붙어있는 코드는 async 함수 내에서 비동기적으로 실행된다는 의미이다.
await은 그 뒤의 코드를 실행하고 그 코드가 return하는 promise 객체가 fulfilled or rejected상태가 될 때까지 기다린다. 만일 promise 객체가 fulfilled 상태가 되면 그 작업 성공 결과를 추출해서 return 한다.
아래의 코드는 async/await를 사용한 간단한 예제이다. 이 코드에서 살펴봐야 할 중요한 점은, async 함수 내부의 await 키워드가 '비동기 실행'을 관리한다는 것이다.
따라서, 코드가 await 키워드를 만나면, 해당 비동기 작업이 완료될 때까지 함수 실행을 일시 정지하고, 다른 코드의 실행을 계속하게 된다.
async function fetchAndPrint() {
console.log(2);
const response = await fetch('https://jsonplaceholder.typicode.com/users');
console.log(7);
const result = await response.text();
console.log(result);
}
console.log(1);
fetchAndPrint();
console.log(3);
console.log(4);
console.log(5);
console.log(6);
위 코드의 출력 순서:
1) 1
2) 2
3) 3, 4, 5, 6 (fetchAndPrint 함수 외부의 동기 코드)
4) 7 (fetch 작업 완료 후)
5) [리스폰스의 내용]
Async/Await 구문에서 예외를 처리하는 가장 일반적인 방법은 try/catch 블록을 사용하는 것이다.
try/catch 블록을 사용하면, try 블록 안에서 async 함수를 호출하고, 해당 함수에서 발생할 수 있는 예외들을 catch 블록에서 잡아내게 되며, 이를 통해 promise 객체가 rejected 상태가 되는 것에 대비할 수 있게 된다.
try 블록 안에는 에러가 발생할 가능성이 있는 코드를 넣는다. async 함수 내부에서 await 키워드를 사용하는 부분이 주로 포함된다.
catch 블록에는 try 블록 내에서 발생한 에러를 처리하는 코드를 작성한다. 이 때, catch 블록은 에러(rejected)가 발생했을 때 실행되며, 발생한 에러 객체에 접근할 수 있다.
finally 블록은 try 블록과 catch 블록 이후에 실행되는 코드 블록이다. 이 블록은 에러의 발생 여부와 관계없이 실행되며, 주로 리소스를 정리하거나 필요한 최종 작업을 수행하는 데 사용된다.
예를 들어, 데이터베이스 연결을 닫거나, 파일 스트림을 종료하거나, 사용자에게 프로세스가 종료되었음을 알리는 UI 업데이트 등이 이에 해당합니다.
cf> try/catch/finally 블록 활용
- try/catch/finally 블록은
네트워크 로딩 처리
를 수행할 때도 자주 사용되는 방법이니 참고로 알아두자.
위와 같이 try/catch 블록을 사용하면, try 블록 안에서 await이 앞에 붙어 있는 프로미스 객체들 중, rejected 상태가 되는 것이 생기면 그 순간 코드의 실행 흐름이 catch문으로 넘어오게 되며, catch문의 파라미터로 promise 객체의 작업 실패 정보가 넘어오게 된다.
아래 코드는 async/await 구문에서 에러 처리를 구현한 예제이다.
아래 코드에서는 fetch 함수를 사용하여 외부 데이터를 가져오는 과정에서 발생할 수 있는 예외를 처리하며, 만약 fetch 과정에서 에러가 발생하면, catch 블록에서 이를 잡아내고 콘솔에 에러 메시지를 출력한다.
async function fetchData() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('데이터를 가져오는 데 실패했습니다:', error);
}
}
fetchData();
async/await 구문의 경우, await은 async 함수 안에서만 사용할 수 있고, 코드의 top-level(어떤 함수 블록 안에 포함된 것이 아닌 코드의 최상위 영역)에서는 사용될 수는 없다.
따라서 코드의 top-level에서 async 함수가 리턴한 Promise 객체의 작업 성공 결과를 가져오려면 await을 사용할 수는 없고, 여전히 then 메소드를 사용해야 한다.
이번 글을 통해 자바스크립트의 비동기 처리 패턴에서 중요한 전환점을 이룬 async/await 구문에 대해 자세히 살펴보았다. 이를 통해, async/await 구문은 개발자들에게 복잡한 Promise 코드를 간결하고 직관적으로 작성할 수 있는 새로운 방법을 제공하였으며, 비동기 프로그래밍의 접근성을 높이고, 이를 더욱 간결하고 효율적으로 사용 가능하게 하였다는 사실을 알 수 있었다.
이 때 주의해야 할 점으로, 위에 글에서 언급하였던 async/await의 주의점들을 유의해야겠지만, 이를 숙지하고 async/await 구문의 올바른 사용을 통해 더욱 효율적인 비동기 프로그램을 구성하는 개발자가 되어보자.