what is Promise? 3. Async / Await

zenibako.lee
9 min readOct 15, 2019

--

자바스크립트 노드 비동기 처리 권장 문법

이번 글은 async/await관련 좋은 글을 발견해서

위 포스팅을 번역해 보았습니다.

Async / Await는 비동기코드를 처리하는 새로운 방법이다.

이전의 대안이라고 하면, 콜백함수를 이용하는 방법과, Promise객체를 이용하는 방법이 있다.

Async/Await는 사실 프로미스 방법을 이용한, 일종의 syntax sugar이다.

async / await는 비동기 코드를 좀 더 동기적 코드로 보이게 한다.

여기에 async/await의 장점이 있다.

Async function은 암시적으로(implicitly) promise를 반환하고,

await function에서 return하는 resolve value도 반환한다.

이제 Async/await의 예제들을 보며 장점들을 살펴보자

1. Concise and clean

promise chaining 의 경우 await/async의 장점이 잘 나타난다.

const makeRequest = () =>
getJSON()
.then(data => {
console.log(data)
return "done"
})

async/await를 사용하면 data를 주고 받을 때, 사용하는 return과 받는 파라미터명 설정등이 생략된다.

const async makeRequest(){console.log(await getJSON())}

2. Error handling

async/await는 error handling을 비동기/동기 같은 방식으로 처리를 가능하게 해 준다.

이전부터 사용해왔던 try/catch 문을 통해서 말이다.

const makeRequest = () => {
try {
getJSON()
.then(result => {
// this parse may fail
const data = JSON.parse(result)
console.log(data)
})
// uncomment this block to handle asynchronous errors
// .catch((err) => {
// console.log(err)
// })
} catch (err) {
console.log(err)
}

promise를 사용한 위 코드의 경우엔,

JSON.parse가 실패할 경우 try/catch가 error handling을 실패한다.

그 이유는

JSON.parse가 try의 대상이 되는 프로미스가 아닌, 그 프로미스 내부에 있는 에러이기 때문에, catch는 그걸 잡지 못한다.

const makeRequest = async () => {
try {
// this parse may fail
const data = JSON.parse(await getJSON())
console.log(data)
} catch (err) {
console.log(err)
}
}

반면에, async/await를 이용해 비동기처리를 구현하고, try/catch로 에러를 잡는다면, 정상적으로 잡힌다.

3. Conditionals

fetch한 결과에 따라 case들을 나눠서 처리해야 하는 경우에도

강점이 있다.

6레벨의 nesting을 보이는 코드인데, 다음 메소드로 값을 넘겨주기 위한 return문의 반복만 세번이 등장한다.

const makeRequest = () => {
return getJSON()
.then(data => {
if (data.needsAnotherRequest) {
return makeAnotherRequest(data)
.then(moreData => {
console.log(moreData)
return moreData
})
} else {
console.log(data)
return data
}
})
}

위 코드를 async/await로 바꾼다면?

const makeRequest = async () => {
const data = await getJSON()
if (data.needsAnotherRequest) {
const moreData = await makeAnotherRequest(data);
console.log(moreData)
return moreData
} else {
console.log(data)
return data
}
}

return문은 case별로 단 한번만 등장하게 된다.

물론 눈에도 case의 구분이 훨씬 보기 편하다.

4. Intermediate value

promise1의 value와 promise2의 value를

promise3의 인자로 넣는 경우,

총 세번의 비동기 함수가 실행이 될텐데, (전체펑션제외)

이것을 Promise객체를 이용해서 나타내보면

const makeRequest = () => {
return promise1()
.then(value1 => {
// do something
return promise2(value1)
.then(value2 => {
// do something
return promise3(value1, value2)
})
})
}

이렇게 된다, 그런데 최종적으로 promise3을 실행할 때에 받는 값은 value2 뿐이다. 이러한 코딩을 싫어하는 사람이라면,

아래와 같이 나눠서 보내줄 수 도 있다.

const makeRequest = () => {
return promise1()
.then(value1 => {
// do something
return Promise.all([value1, promise2(value1)])
})
.then(([value1, value2]) => {
// do something
return promise3(value1, value2)
})
}

그런데 사실 이 방법은, promise3의 파라미터가 무엇인지 보여주는 가독성을 위해, 굳이 value1과 value2가 배열에 들어가는 작업을 하고 있다. 어떤 의미로는 무의미한 작업이 추가된 것이다.

그런데 이런 코드를 await / async를 통해 써보면

const makeRequest = async () => {
const value1 = await promise1()
const value2 = await promise2(value1)
return promise3(value1, value2)
}

직관적이고 깔끔하다..

5.Error Stack

연속적인 프로미스들을 chaining한 function이 있을 때,

에러가 발생한 경우, 기존의 방식 (promise)을 통해 구현한 경우

const makeRequest = () => {
return callAPromise()
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => {
throw new Error("oops");
})
}

makeRequest()
.catch(err => {
console.log(err);
// output
// Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)

정확히 어떠한 then(return promise)에서 에러가 발생한지는

나타나지 않는다.

반면에, Async/await의 경우

const makeRequest = async () => {
await callAPromise()
await callAPromise()
await callAPromise()
await callAPromise()
await callAPromise()
throw new Error("oops");
}

makeRequest()
.catch(err => {
console.log(err);
// output
// Error: oops at makeRequest (index.js:7:9)
})

정확히 어디에서 throw Error하고 있는지 확인할 수 있다.

로컬환경에서 개발을 할 때는, 큰 장점으로 보이지 않지만,

서버에서 개발을 하다 에러가 난 경우, catch를 통해 에러의 위치를 확인하는 것은 매우 큰 장점이다. then..then..에서 발생하는 에러를 확인하는 것 보다.

6. 디버깅

마지막 장점이지만, 전혀 작지 않은 장점이다.

async/await를 이용하면 디버깅이 매우 쉬워진다는 것이다.

프로미스 디버깅은 2가지 이유로 큰 고통을 유발한다.

  1. arrow function으로 만들어진 expression return function의 내부에서
    breakpoint를 설정할 수 없다.

2. then 블록 내부에 breakpoint를 설정하고,

step-over와 같은 debug shortcut 기능을 사용하면,

디버거는 다음 .then으로 진입하지 않는다.

그 이유는 step은 동기코드에만 해당하기 때문이다.

async/await의 경우 arrow function이 그다지 필요하지도 않을 뿐더러,

마치 동기코드처럼 await call내부로 진입이 가능하다.

--

--

zenibako.lee
zenibako.lee

Written by zenibako.lee

backend engineer, JS, Node, AWS

No responses yet