필자의 경우 nestjs로 백앤드를 개발하고 있는데 개발을 하다 보면 비동기 함수를 사용하는 경우가 많고 그럼 async 선언을 많이 하는데 이 함수들을 사용할 때 return에 대해서 어떻게 처리하는 게 올바른가에 대한 생각을 자주 하게 된다. 그럴 때마다 구글에서 검색하면 어떤 외국 원문이 굉장히 잘 설명되어 있고 그 글을 해석한 글들이 굉장히 많이 있기 때문에 볼 때마다 이해했다고 착각? 하고 그 순간만 넘기다 보니 똑같은 주제를 3번 이상 찾아보고 있다는 걸 느꼈고 내용은 같더라도 직접 정리해서 내 거로 만들고 다시 검색하는 일을 없애기 위해 작성하는 글이다!! 부디 기존 글들과 같다고 나쁘게 생각하지 마세요!!! 이 글을 보시는 분들도 이러한 생각들을 고민한 적이 있다면 주저 없이 작성하고 정리하세요!! 그게 여러분 그리고 우리를 성장시킬 겁니다!!!!
실제 내가 옛날에 작성한 코드들과 현재 코드들을 비교해 보면 과거 코드들은 return await something 처리를 하고 현재 코드들은 return something으로 처리하는 경우가 많은데 과거에는 이런 비동기에 대한 이해가 부족한 상태에서 사용하다 보니 무조건 async 함수는 return await을 해야 한다 이런 강박을 가지고 전부 붙였고 최근 코드들은 정확히 이해하고 사용하고 있다고 착각에 빠져 await을 전부 빼고 사용하고 있었다. 정확히 알고 사용하고 있었던 것이 아니다.
이제부터 원문을 바탕으로 async 함수를 작성할 때 사용하는 await, return await, return에 대해 알아보자 원문의 링크는 맨 아래에 첨부!
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!');
}
이 함수는 50% 확률로 fulfilling(이행) 'yay'를 던지거나 rejecting(실패) error를 던진다.
Just Calling
async function foo() {
try {
waitAndMaybeReject();
} catch (e) {
return 'caught';
}
}
여기서 foo를 호출하면 항상 undefined인 Fulfilled 상태의 Promise를 리턴할 것이다 기다림 없이
왜냐하면 위 함수는 waitAndMaybeReject() 함수를 await이나 return 처리를 하지 않았기 때문이다.
만약 이 예시 자체를 이해하기 힘들다면 일단 비동기가 돌아가는 구조 먼저 다시 공부하고 오면 더 좋을 것입니다!!!
Awaiting
async function foo() {
try {
await waitAndMaybeReject();
} catch (e) {
return 'caught';
}
}
여기서 foo를 호출하면 항상 1초를 기다린 뒤 undefined를 리턴하거나 'caught'를 리턴할 것이다.
왜냐하면 위 코드에서 waitAndMaybeReject() 함수에 await 처리를 하기 때문에 반환 값이 rejection일 경우 throw가 catch 블록에서 처리되어 'caught'를 리턴하게 되는 데 waitAndMaybeReject() 함수가 Fulfilled 상태를 리턴할 경우 우리는 await 처리만 할 뿐 아무런 동작을 하지 않기 때문에 값을 받을 수 없다.
Returning
async function foo() {
try {
return waitAndMaybeReject();
} catch (e) {
return 'caught';
}
}
여기서 foo를 호출하면 항상 1초를 기다린 뒤 Fullfilled 상태의 "yay"를 리턴하거나 reject 상태의 Error('Boo!')를 리턴할 것이다.
waitAndMaybeReject()를 await 하지 않고 바로 리턴을 했기 때문에 catch 블록은 절대 실행되지 않는다.
Return-awaiting
async function foo() {
try {
return await waitAndMaybeReject();
} catch (e) {
return 'caught';
}
}
여기서 foo를 호출하면 항상 1초를 기다린 뒤 Fullfilled 상태의 "yay"를 리턴하거나 "caught"를 리턴할 것이다.
왜냐하면 waitAndMaybeReject()에 await 처리를 했기 때문에 reject상태를 리턴한다면 catch 블록이 처리할 것이며 Fullfilled 상태를 리턴한다면 return 처리를 했기 때문에 값을 얻을 수 있기 때문이다.
위 내용들이 혼란스럽다면 아래와 같이 두 단계로 생각하면 좋을 것이다.
async function foo() {
try {
// waitAndMaybeReject()의 결과를 기다린 뒤
// 그 값을 fulfilledValue에 할당
const fulfilledValue = await waitAndMaybeReject();
// 그리고 fulfilledValue을 return
return fulfilledValue;
} catch (e) {
return 'caught';
}
}
Note : async 함수의 리턴값은 항상 Promise.resolve로 wrapped 되어 있기 때문에 return await는 실효성이 없다. 다만 try/catch 구문에서는 다른 Promise 함수의 오류를 catch 하기 위해 사용될 수 있다고 한다.
처음에는 잘 이해하기 힘들 수 있다. 필자도 그랬다 최소 3번 이상 읽었을 때 감이 왔고 이해했다. 그러니 조급하게 생각하지 말고 코드부터 글까지 차근차근 읽고 실제로 설명을 보기 전에 어떻게 돌아갈지 상상하면서 글을 다시 읽어보고 또 자신에 코드에 반영하길 추천한다 그럼 정말 자신의 것이 되어 있을 것이다!!
원문 링크
https://jakearchibald.com/2017/await-vs-return-vs-return-await/
'개발 > JavaScript' 카테고리의 다른 글
[TypeScript] 타입 공간과 값 공간의 심벌 구분하기! (0) | 2023.02.26 |
---|---|
[JavaScript] 옵셔널 체이닝(Optional chaining) 사용법과 주의할점 (0) | 2023.02.13 |
[JavaScript] call by value, call by reference, call by sharing (0) | 2023.02.07 |
[JavaScript] 인자 개수가 자유로운 함수 선언(arguments) (0) | 2023.02.05 |
[JavaScript] parameter vs argument (0) | 2023.02.04 |
댓글