You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
async/await 키워드는 ES8에서 도입된 비동기 처리를 위한 문법으로, 프로미스를 기반으로 하지만 then과 catch 메서드를 사용하지 않고 비동기 작업을 수행할 수 있다. async/await 키워드를 사용하면 비동기 작업을 마치 동기 작업처럼 쓸 수 있어서 코드가 간결하고 가독성이 좋아지게 된다.
async & await
async/await는 ES2017에 도입된 문법으로서, Promise 로직을 더 쉽고 간결하게 사용할 수 있게 해준다.
유의해야 할 점이 async/await가 Promise를 대체하기 위한 기능이 아니다.
내부적으로는 여전히 Promise를 사용해서 비동기를 처리하고, 단지 코드 작성 부분을 프로그래머가 유지보수하게 편하게 보이는 문법만 다르게 해줄 뿐이라는 것이다.
async / await 기본 사용법
async 와 await 는 절차적 언어에서 작성하는 코드와 같이 사용법도 간단하고 이해하기도 쉽다.
await 키워드는 오직 async 로 정의된 함수의 내부에서만 사용될 수 있다.
function 키워드 앞에async 만 붙여주면 되고, 비동기로 처리되는 부분 앞에 await 만 붙여주면 된다.
모든 async 함수는 암묵적으로 promise를 반환하고, promise가 함수로부터 반환할 값을 resolve 한다.
// 기존 Promise.then() 형식functionmain(){delay(1000).then(()=>{returndelay(2000);}).then(()=>{returnPromise.resolve("끝");}).then((result)=>{console.log(result);});}// 메인 함수 호출main();
위 코드에서 promise는 then 메서드를 연속적으로 사용하여 비동기 처리를 하지만, async/await는 await 키워드로 비동기 처리를 기다리고 있다는 것을 직관적으로 표현하고 있음을 볼 수 있다.
// async/await 방식asyncfunctionmain(){awaitdelay(1000);awaitdelay(2000);constresult=awaitPromise.resolve("끝");console.log(result);}// 메인 함수 호출main();
async/await의 장점은 비동기적 접근방식을 동기적으로 작성할 수 있게 해주어 코드가 간결해지며 가독성을 높여져 유지보수를 용이하게 해준다.
async 키워드
function 앞에 async을 붙여줌으로써, 함수 내에서 await 키워드를 사용할 수 있게 된다. 이는 반대로 말하면 await 키워드를 사용하기 위해선 반드시 async function 정의가 되어 있어야 한다는 말과 같다.
// 함수 선언식asyncfunctionfunc1(){constres=awaitfetch(url);// 요청을 기다림constdata=awaitres.json();// 응답을 JSON으로 파싱}func1();// 함수 표현식constfunc2=async()=>{constres=awaitfetch(url);// 요청을 기다림constdata=awaitres.json();// 응답을 JSON으로 파싱};func2();
await는 promise.then() 보다 깔끔한 코드(?)로 비동기 처리의 결과값을 얻을 수 있도록 해주는 문법이다.
예를들어 서버에 리소스를 요청하는 fetch() 비동기 함수를 then 핸들러 방식으로 결과를 얻어 사용해왔을 것이다.
// then 핸들러 방식fetch(url).then((res)=>res.json())// 응답을 JSON으로 파싱.then((data)=>{// data 처리console.log(data);});
await 키워드를 사용하면 then 핸들러를 복잡하게 처리할 필요 없이, 심플하게 비동기 함수 왼쪽에 await만 명시해주고 결과값을 변수에 받도록 코드를 정의하면 끝이다.
then과 콜백 함수를 남발하여 코드가 들여쓰기로 깊어지는 것을 방지하고, 한 줄 레벨에서 코드를 나열하여 가독성을 높일 수 있다.
// await 방식asyncfunctionfunc(){constres=awaitfetch(url);// 요청을 기다림constdata=awaitres.json();// 응답을 JSON으로 파싱// data 처리console.log(data);}func();
await는 Promise 처리가 끝날때까지 기다림
await은 Promise 비동기 처리가 완료될때 까지 코드 실행을 일시 중지하고 wait 한다라는 뜻이다.
예를 들어 fetch() 함수를 사용하여 서버에서 데이터를 가져오는 경우를 떠올려보면, 이 fetch() 함수는 Promise를 반환한다. 따라서 await 키워드를 사용하여 Promise가 처리될 때까지 코드 실행을 일시 중지하고, Promise가 처리되면 결과 값을 반환하여 변수에 할당하는 식이다.
위 예제를 보면 getData() async 함수 내에서 fetch() 비동기 함수를 호출하고, 반환된 Promise를 await 키워드로 처리한다.
이로 인해 함수 내 코드 실행이 일시 중지되고, fetch() 함수가 완료될 때까지 기다리게 된다. → 서버로부터 리소스를 성공적으로 가져와 fetch() 함수가 완료되면, 바로 다음response.json() 함수를 호출하여 반환된 Promise를 다시 await로 처리한다. → 다시 데이터를 가져오는 동안 코드 실행이 일시 중지되고, 데이터가 성공적으로 가져와지면 최종 결과 값을 반환한다.
따라서 await는 Promise를 처리하고 결과를 반환하는 데, 비동기적인 작업을 동기적으로 처리할 수 있게 되는 것이다.
async/await 에러 처리
기존의 Promise.then() 방식의 에러 처리는 catch() 핸들러를 중간 중간에 명시함으로써 에러를 받아야만 했고, 일반적으로 에러를 처리하기 위해선 try/catch 문을 사용하여 에러를 처리해왔다 .
// then 핸들러 방식functionfetchResource(url){fetch(url).then((res)=>res.json())// 응답을 JSON으로 파싱.then((data)=>{// data 처리console.log(data);}).catch((err)=>{// 에러 처리console.error(err);});}
async/await도 비동기 처리에 대한 에러를 처리할 필요가 생기면 그대로 try/catch문을 씌우면 된다.
// async/await 방식asyncfunctionfunc(){try{constres=awaitfetch(url);// 요청을 기다림constdata=awaitres.json();// 응답을 JSON으로 파싱// data 처리console.log(data);}catch(err){// 에러 처리console.error(err);}}func();
이처럼 async/await의 장점은 비동기 코드를 마치 동기 코드처럼 읽히게 해준다는 것이다. 우리가 일반적으로 코드를 쓰고 읽어 내리듯이
적절한 async/await 사용
await 키워드를 사용하면 비동기가 강제적으로 동기 처리가 되어 코드가 순차적으로 수행된다. 그러면 이를 어떻게 ‘병렬 처리’ 한다는 것일까? 핵심은 프로미스 객체 함수를 await과 같이 써서 실행시키는 게 아니라, 미리 함수를 동기/논블록킹으로 실행하고 그 결과 프로미스 값을 await를 통해 받는 식이다.
기존에는 비동기 처리 요청을 하고 동시에 요청이 완료될때 까지 await 하였기 때문에 1초안에 처리될 것이 2초가 걸렸다.
asyncfunctiongetFruites(){leta=awaitgetApple();// getApple() 비동기 처리를 요청하고, 요청이 처리될때 까지 기다림 (1초 소요)letb=awaitgetBanana();// getBanana() 비동기 처리를 요청하고, 요청이 처리될때 까지 기다림 (1초 소요)console.log(`${a} and ${b}`);// 총 2초 소요}
getApple() 와getBanana() 비동기 로직이 순서를 지켜야하는 로직이라면 위와 같이 구성하여야 하는 것이 옳지만, 현재로서는 서로 연관 없기 때문에 반드시 순차적으로 실행 시킬 필요가 없다. 따라서 비동기 처리 요청과 값을 await 하는 로직을 분리시키면 된다.
asyncfunctiongetFruites(){letgetApplePromise=getApple();// async함수를 미리 논블록킹으로 실행한다.letgetBananaPromise=getBanana();// async함수를 미리 논블록킹으로 실행한다.// 이렇게 하면 각각 백단에서 독립적으로 거의 동시에 실행되게 된다.console.log(getApplePromise)console.log(getBananaPromise)leta=awaitgetApplePromise;// 위에서 받은 프로미스객체 결과 변수를 await을 통해 꺼낸다.letb=awaitgetBananaPromise;// 위에서 받은 프로미스객체 결과 변수를 await을 통해 꺼낸다.console.log(`${a} and ${b}`);// 본래라면 1초+1초 를 기다려야 하는데, 위에서 1초기다리는 함수를 바로 연속으로 비동기로 불려왔기 때문에, 대충 1.01초만 기다리면 처리된다.})
Async/Await 내부 동작 과정
const one = () => Promise.resolve('One!');
async function myFunc(){
console.log('In function!');
const res = await One();
console.log(res);
}
console.log('Before Function!');
myFunc();
console.log('After Function!');
콘솔에 'Before Function!' 이 출력된다.
async 함수인myFunc() 이 호출된다.
async 함수 안에 있는 콘솔 함수가 실행되어 콘솔에 'In Function!' 이 출력된다.
Promise 객체를 반환하는one() 비동기 함수를 호출한다.
이때one() 비동기 함수 왼쪽에await 키워드로 인해,myFunc 함수의 내부 실행은 잠시 중단되고 Call stack 에서 빠져나와 나머지 부분은 Microtask Queue 에 적재된다. 이는 자바스크립트 엔진이await 키워드를 인식하면 async 함수의 실행은 지연되는 것으로 처리하기 때문이다.
마지막으로 콘솔에 'After Function!' 이 출력된다.
모든 메인 스레드의 자바스크립트 코드가 실행이되어 더이상 Call Stack엔 실행할 스택이 없어 비워지게 된다.
그러면 이벤트 핸들러가 이를 감지하여, Microtask Queue에 남아있는 async 함수를 빼와 Call Stack에 적재하게 된다.
Promise 객체의 결과물인 'One!' 문자열을 변수res 에 받고 이를 콘솔에 출력한다.