
비동기 함수를 작성할 때 await과 return을 하는 것 그리고 return await을 사용하는 것에는 실행에 차이가 있다. 이것을 올바르게 사용하지 않으면 우리는 예상치 못한 값을 반환받을 수 있다. 이 포스팅은 그러한 버그를 미리 방지하고 비동기에 대한 이해를 확장하고자 작성한다.
아래는 async함수이다.
async function waitAndMaybeReject() {
// Wait one second
await new Promise(r => setTimeout(r, 1000));
// Toss a coin
const isHeads = Boolean(Math.round(Math.random()));
if (isHeads) return 'yay';
throw Error('Boo!');
}
이 함수는 setTimout으로 1초간 대기하는 promise를 비동기적으로 실행하고, 50/50의 확률로 'yay'를 return 하거나 에러를 발생시켜서 'Boo!'를 return 한다. 해당 함수를 이용해서 밑에서 비동기적으로 어떻게 실행결과가 다른지 확인해보도록 하겠다.
Just calling
async function foo() {
try {
waitAndMaybeReject();
}
catch (e) {
return 'caught';
}
}
만약 foo() 함수를 호출하게 된다면 어떤 값을 반환하게 될까?
만약 'yay' 또는 'Boo!' 값이 return 되길 기대했다면 아직 비동기에 대한 이해가 부족한 상태라고 할 수 있다. 이 함수를 실행하게 되면 Promise 값을 return 하게 된다.
왜냐하면 waitAndMaybeReject() 함수는 async를 이용해 비동기적으로 작성되었다. 그러므로 foo() 함수에서도waitAndMaybeReject() 함수는 비동기적으로 호출되는데, 그 결과 함수의 반환 값을 기다리지 않고(promise가 resolve되기 전에) 바로 다음 코드를 실행하기 때문에 catch블록에 잡힐 값이 없으므로 promise만이 return 된다.
Awaiting
async function foo() {
try {
await waitAndMaybeReject();
}
catch (e) {
return 'caught';
}
}
그렇다면 await를 붙여서 waitAndMaybeReject() 함수의 반환 값을 기다리게 한다면 어떻게 될까?
foo() 함수를 호출하게 되면 waitAndMaybeReject()가 비동기적으로 호출되며, promise는 setTimout으로 인해 1초를 기다리게 된다. 그리고 50/50 확률로 undefined가 실행되거나 'caught'가 실행될 것이다.
왜냐하면 만약 waitAndMaybeReject() 함수가 reject 된다면 catch 블록이 에러를 잡아서 'caught'를 return 할 것이고, 만약 문제없이 실행된다면 아무런 값도 반환되지 않을 것이기 때문이다.
Returning
async function foo() {
try {
return waitAndMaybeReject();
}
catch (e) {
return 'caught';
}
}
이번엔 await를 없애고 바로 return 시키는 형태의 함수이다. foo() 함수를 실행시키면 waitAndMaybeReject() 함수의 반환 값을 foo() 함수에서 반환하기 위해 함수가 실행 완료 될때까지 기다리게 된다.
그 결과 waitAndMaybeReject() 함수 내부에서는 1초를 기다리게 되고 return 값이 'yay'가 return 되거나, 50/50의 확률로 Error('Boo!')가 return 된다.
waitAndMaybeReject()을 return 하게 되면 결과 값이 바로 return 되기 때문에 catch 블록에서 에러가 잡히지 않는다.
Return-awaiting
아마 이 형태의 함수가 우리가 본질적으로 가장 원하는 형태의 함수일 것이다.
async function foo() {
try {
return await waitAndMaybeReject();
}
catch (e) {
return 'caught';
}
}
foo() 함수를 실행시키면 promise는 1초를 기다린다. 그리고 'yay'또는 catch블록의 'caught'를 return 하게 된다.
왜냐하면 우리는 await으로 인해서 waitAndMaybeReject() 함수를 기다리게 될 것이다. 그리고 만약 err가 throw 된다면 catch 블록에서 잡혀서 'caught'를 실행하게 될 것이다. 또한 에러 없이 실행이 된다면 'yay'라는 결과 값을 return 하게 될 것이다.
만약 위의 코드들이 헷갈린다면 아래의 두 가지 단계로 생각하면 이해하기 편리하다.
1. waitAndMaybeReject()의 결과를 기다리고, fullfilledValue에 그 값을 초기화한다.
2. 만약 결과 값이 reject라면 catch블록에 잡혀서 'caught'가 return 되고, resolve라면 fullfilledValue의 값이 return 된다.
async function foo() {
try {
// Wait for the result of waitAndMaybeReject() to settle,
// and assign the fulfilled value to fulfilledValue:
const fulfilledValue = await waitAndMaybeReject();
// If the result of waitAndMaybeReject() rejects, our code
// throws, and we jump to the catch block.
// Otherwise, this block continues to run:
return fulfilledValue;
}
catch (e) {
return 'caught';
}
}
비동기는 결코 쉬운 개념이 아니다. 시간을 들여 이해해야 하는 영역이고, 필자 역시 비동기에 관한 100% 이해도를 가졌다고 말할 수 없다. 하지만 분명 비동기는 컴퓨팅 자원을 효율적으로 사용할 수 있고, 많은 부분에서 이득을 볼 수 있다. 그러므로 앞으로 동기적인 프로그래밍을 하게 될지라도 비동기적인 프로그래밍과 이에 대한 이해는 분명 좋은 개발자가 되는 데에 인사이트를 줄 것이다.
ref
https://jakearchibald.com/2017/await-vs-return-vs-return-await/
'Server > Node.js (Express)' 카테고리의 다른 글
[Node.js] Sequelize : ORM(Object-relational Mapping) 사용해보기 (0) | 2019.12.16 |
---|---|
[Node.js] CRUD: foreign key를 이용해 blog 구조 만들기 (2) (2) | 2019.10.30 |
[Node.js] CRUD: RDS를 이용하여 동적으로 구성하기 (1) (7) | 2019.10.30 |
[Node.js] Express와 CSV를 이용해 조 편성 애플리케이션 만들기 (1) | 2019.10.17 |
[Node.js] npm이란? (0) | 2019.10.11 |