-
비동기 프로그래밍에 관하여 - 3. async / awaitfront-end/ES6 2022. 2. 21. 10:23728x90
Promise를 사용하면 체이닝을 통해 순차적으로 코드를 수행할 수 있다.
다만, 체이닝이 길어지고 복잡해진다면 코드 가독성이 떨어질 수 있다.
async와 await를 사용하면 마치 동기식 코드를 짜는거 마냥 간편하게 코드를 작성할 수 있다.
async / await은 새로운 기능을 추가하는 것이 아니라 기존의 promise에서 간편한 api를 제공한다.
이와 같이 기존에 존재하던 기능 위에 편리한 기능을 감싸서 사용하는 것을 syntactic sugar라고 한다.
사람이 프로그래밍 언어를 조금 더 '달콤하게'
이해하기 쉽고 간결하게, 명확하게 도와주는 문법이라고 생각하면 될 것같다.
async와 & await은 promise를 보다 깔끔하게 사용할 수 있는 방법이다.
그렇다고해서 promise가 나쁘다거나 async & await로 대체해서 사용해야하는 것이 아니다.
function fetchUser() { return new Promise((resolve, reject) => { //do network request in 10 secs... resolve("username"); }); } const user = fetchUser(); user.then(console.log);
위와 같은 비동기적 실행을 위한 코드가 있을 때, 이 코드를 수정하여보자.
async function fetchUser() { return 'username' }
자신이 비동기적으로 처리할 함수에 'async'키워드를 붙혀주면 자동적으로 Promise로 변환이 된다.
똑같이 Promise 객체를 반환한다. 만약 async 키워드로 만들어진 함수 내부에서 동기적인 코드를 작성하고 싶다면 await 키워드를 사용하면 된다.
function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } async function getSomething1() { await delay(3000); return "something1"; } async function getSomething2() { await delay(3000); return "something2"; }
await는 콜백지옥을 해결하는데에도 도움이 된다.
function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } async function getSomething1() { await delay(3000); return "something1"; } async function getSomething2() { await delay(3000); return "something2"; } function getAll() { return getSomething1.then((one) => { return getSomething2.then((two) => `${one} + ${two}`); }); } getAll.then(console.log);
위와 같이 promise끼리 콜백으로 물려있는 상황이 있을 수 있다.
function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } async function getSomething1() { await delay(3000); return "something1"; } async function getSomething2() { await delay(3000); return "something2"; } async function getAll() { const one = await getSomething1(); const two = await getSomething2(); return `${one} + ${two}`; } getAll.then(console.log);
async와 await 키워드만 사용했을 뿐인데, 우리가 익숙한 느낌으로, 마치 동기적인 코드를 작성할 수 있다.
그런데 위의 코드를 살펴보면 큰 문제점이 하나 있다.
바로 비동기적인 실행들이 모두 연결되어 있어, 병렬적 처리가 되지 않는다는 것이다.
getAll() 함수를 통해 결과값을 받아오기 위해서는 3000ms + 3000ms, 총 6초를 꼬박 기다리고 있어야한다.
하지만, 각각의 처리가 서로를 기다려줄 필요는 없다.
웹 페이지에서 서버와 통신하는 api가 늘어날 수록 페이지가 표시되기까지 걸리는 시간은 어마어마하게 늘어나버릴것이다.
해결방안 중 하나는 임의로 Promise 객체를 만들어버리는 것이다.
promise 객체에 작성되어있는 코드 excutor는 promise가 생성되자마자 실행되는 특징이 있다.
function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } async function getSomething1() { await delay(3000); return "something1"; } async function getSomething2() { await delay(3000); return "something2"; } async function getAll() { const promise1 = getSomething1(); const promise2 = getSomething2(); const one = await promise1; const two = await promise2; return `${one} + ${two}`; } getAll().then(console.log);
병렬적으로 코드가 실행되어 대략 3000ms가 걸렸다. 하지만 비동기적인 코드가 늘어날수록 쓸데없이 promise객체를 생성하는 지저분한 코드가 될 것이다.
그렇기에 promise에서는 이를 해결하기 위한 all() 메서드를 제공한다.
각각의 promise 값들을 배열으로 묶어서 내보낸다.
async function getAll() { const promise1 = getSomething1(); const promise2 = getSomething2(); const one = await promise1; const two = await promise2; return `${one} + ${two}`; } getAll().then(console.log); ///////////////////////////////////////////// function getAll2() { return Promise.all([getSomething1(), getSomething2()]).then((result) => result.join(" + ") ); } getAll2().then(console.log);
getAll()과 getAll2()는 동일한 결과를 반환한다.
병렬적인 처리를 진행하면서 가장 처음으로 완료되는 하나의 결과만 반환받을수도 있다.
Promise.race() 메서드를 사용하면 된다.
function getOnlyOne() { return Promise.race([getSomething1(), getSomething2()]); } getOnlyOne().then(console.log);
지난 시간 Promise의 체이닝을 통해 작성한 코드가 있었다.
class UserStorage { loginUser(id, password) { return new Promise((resolve, reject) => { setTimeout(() => { if ( (id === "user" && password === "user") || (id === "admin" && password === "admin") ) { resolve(id); } else { reject(new Error("not found")); } }, 2000); }); } getRoles(user) { return new Promise((resolve, reject) => { setTimeout(() => { if (user === "admin") { resolve({ name: "admin", role: "admin" }); } else { reject(new Error("no access")); } }, 1000); }); } } const userStorage = new UserStorage(); const id = prompt("enter user id"); const password = prompt("enter user password"); userStorage .loginUser(id, password) .then(userStorage.getRoles) .then((user) => alert(`hello ${user.name}, you have a ${user.role} role`)) .catch(console.log);
위의 코드를 async와 await를 활용하여 수정하면 다음과 같다. (맨 아래부분 실행 로직만 변경시키겠다.)
const userStorage = new UserStorage(); const id = prompt("enter user id"); const password = prompt("enter user password"); //1. Promise userStorage .loginUser(id, password) .then(userStorage.getRoles) .then((user) => alert(`hello ${user.name}, you have a ${user.role} role`)) .catch(console.log); //2. async & await async function checkUser() { try { const userId = await userStorage.loginUser(id, password); const user = await userStorage.getRoles(userId); alert(`hello ${user.name}, you have a ${user.role} role`); } catch (error) { console.log(error); } }
728x90'front-end > ES6' 카테고리의 다른 글
널 병합 연산자(Nullish coalescing operator / ??) (0) 2022.02.21 개체 참조 및 복사 (0) 2022.02.21 비동기 프로그래밍에 관하여 - 2. 프로미스(Promise) (0) 2022.02.21 비동기 프로그래밍에 관하여 - 1. 콜백(Callback) (0) 2022.02.21 화살표 함수(Arrow Function) (0) 2022.02.21