2015년 이후 프론트엔드 엔지니어가 되었다면, 아마도 'Promise'를 사용해봤거나 들어본 적이 있을 것입니다. 이전 블로그에서 다룬 바와 같이, 'Promise'는 비동기 프로그래밍에서 연산 간 순차적 관계를 읽기 쉽고 유지보수 가능하게 명시하는 데 도움을 줍니다. JavaScript의 'Promise' 객체는 'then', 'catch'와 같은 메서드를 가지고 있어, 연산의 순차적 실행을 Promise Chaining이라는 패턴으로 조직하는 데 도움을 줍니다. Promise Chaining이 깔끔하고 체계적이긴 하지만, 특히 동기식 프로그래밍에 익숙한 사람들은 직관적이지 않고 혼란스럽다고 느낍니다. 다행히도 ECMAScript 2017은 비동기 코드를 동기식으로 작성할 수 있는 새로운 기능을 도입했습니다. 이 새로운 기능은 두 가지 키워드로 구성되어 있습니다: 'async'와 'wait'입니다. 이 글에서는 '비동기/대기(async/await)'의 기본 원리와 약속 체인(Promise Chaining)에서 '비동기/대기(async/waitit)' 사용으로 전환하는 방법을 다루겠습니다.
기다리기: 약속이 이루어질 기다림
먼저 'await' 키워드부터 시작하겠습니다. 'Promise' 객체를 반환하는 함수를 호출할 때 'wait'를 사용합니다. 이 함수는 이 'Promise' 객체가 완료(또는 실패)될 때까지 모든 후속 작업을 차단합니다. 또한 'Promise' 객체의 이행 값을 상수나 변수에 할당하는 데도 도움을 줄 수 있습니다. 다음은 그 주요 용도를 설명하기 위한 예시입니다.
function getNumberOneInOneSecond() {
return new Promise((resolve) => {
setTimeout(() => {
결의(1)
}, 1000)
})
}
'getNumberOneInOneOneOneSecond'라는 함수가 있다고 가정해 봅시다. 이 함수는 'Promise' 객체를 반환합니다. 이 'Promise' 객체를 구성할 때, 인자가 해결 함수인 익명 함수를 생성자에게 전달합니다. 익명 함수에서는 'setTimeOut'을 사용해 1초 후에 인수를 1인 'resolve' 함수로 호출합니다.
반환된 값인 'getNumberOneInOneOneSecond'로 상수를 초기화하면, 그 상수는 'Promise'의 인스턴스가 됩니다.
function getNumberOneInOneSecond() {
return new Promise((resolve) => {
setTimeout(() => {
결의(1)
}, 1000)
})
}
cont numberOnePromise = getNumberOneInOneOneSecond()
console.log(numberOnePromise 인스턴스 오브 프레이스)
하지만 직관적으로는 상수가 1번이어야 한다고 예상합니다. 여기서 'wait' 키워드가 유용합니다. 'wait' 키워드를 사용해 'Promise' 객체의 이행 값을 상수 'numberOne'에 직접 할당할 수 있습니다. 앞서 언급했듯이, 'wait'는 'Promise' 객체가 아직 대기 중일 때 모든 후속 작업을 차단합니다. 따라서 콘솔은 'Promise' 객체가 완료된 후에만 상수 'numberOne'을 출력합니다.
function getNumberOneInOneSecond() {
return new Promise((resolve) => {
setTimeout(() => {
결의(1)
}, 1000);
})
}
비동기 함수 main() {
console.log('1번 얻기...') // 즉시 실행
const numberOne = waitit, getOneInOneOneSecond()
console.log(넘버원)
}
main()
비동기: 기다림을 위한 성소이자 약속을 생산하는 공장
'await' 키워드는 비동기식 프로그래밍에서 동기식 코드를 작성하는 데 도움을 주지만, 원하는 곳에서 사용할 수는 없습니다. 왜 'waitit getOneInOneOneSecond()'를 'main' 함수에 감싸고 위 코드 예시에서는 'async'로 표시했는지 혼동하실 수 있습니다. 사실 저는 단순히 재미로 한 게 아닙니다. 'async'로 표시된 함수에서만 'wait' 키워드를 사용할 수 있기 때문에 어쩔 수 없었습니다.
또한 'async'는 일반 함수를 'Promise' 객체를 반환하는 함수로 변환할 수 있습니다. 'multiplyByTwo'라는 함수가 있다고 가정해 봅시다. 이 함수는 하나의 인자를 받아들입니다. 먼저, 인수가 숫자인지 확인합니다. 인수가 숫자라면, 'multiplyByTwo'가 2를 곱해 곱한 값을 반환합니다. 그렇지 않으면 'multiplyByTwo'가 오류를 던집니다.
function multiplyByTwo(input) {
만약 (typeof input === "number") {
const output = input * 2
출력 반환
} 그렇지 않으면 {
const error = 새로운 오류("Invalid Input")
던지기 오류
}
}
함수 앞에 'async' 키워드를 추가하면, 함수는 'Promise' 객체를 반환하는 함수가 됩니다. 반환된 'Promise' 객체는 원래 함수의 반환 값으로 해결되고, 원래 함수가 던진 오류로 거부됩니다.
async 함수 multiplyByTwo(input) {
만약 (typeof input === "number") {
const output = input * 2
출력 반환
} 그렇지 않으면 {
const error = 새로운 오류("Invalid Input")
던지기 오류
}
}
이는 이전 블로그에서 예시로 사용한 다음 함수와 동등합니다.
function multiplyByTwo(input) {
return new Promise((resolve, reject) => {
만약 (typeof input === "number") {
const output = input * 2
resolve(출력)
} 그렇지 않으면 {
const error = 새로운 오류("Invalid Input")
reject(오류)
}
})
}
보시다시피, '비동기' 키워드는 비동기 프로그래밍에서 동기식 함수를 작성할 수 있게 해줍니다. 함수에서 Promise를 반환할 때마다 'Promise' 객체를 생성하는 것을 해방시킬 뿐만 아니라, 코드베이스에 'resolve'나 'reject'와 같은 익숙하지 않은 함수를 포함하는 것을 피하는 데도 도움이 됩니다.
시도/잡기: 동기식 방식으로 오류를 처리할 수도 있습니다
이 시점에서 당신은 "캐치 방법을 사용하지 않고 오류를 어떻게 처리할 수 있을까?"라고 물을 수 있습니다. 답은 놀라울 정도로 간단합니다! 우리는 오랜 친구인 'try/catch' 개념을 사용할 수 있습니다.
async 함수 multiplyByTwo(input) {
만약 (typeof input === "number") {
const output = input * 2
출력 반환
} 그렇지 않으면 {
const error = 새로운 오류("Invalid Input")
던지기 오류
}
}
비동기 함수 main() {
시도해 {
const output = wait, multiplyByTwo("2")
console.log(출력)
} 잡기 (e) {
console.log(e)
}
}
main()
'multiplyByTwo'가 잠재적으로 던질 수 있는 오류를 처리하기 위해, 호출을 'try' 블록으로 감싸고, 오류 처리 연산은 'catch' 블록에서 정의합니다(저희 경우에는 단순히 오류를 표시합니다).
비동기/기다리기: 약속의 사슬에서 벗어나
다음으로, 약속 체인을 활용한 다음 코드 예제를 async/wait를 어떻게 다시 쓸 수 있는지 시연하겠습니다.
function addExclamationMark(input) {
return new Promise((resolve, reject) => {
만약 (typeof input === "string") {
const output = input + "!"
resolve(출력)
} 그렇지 않으면 {
const error = 새로운 오류("Invalid Input")
reject(오류)
}
})
}
addExclamationMark("안녕, 세상")
.then(addExclamationMark) // .then((input) => addExclamationMark(input)와 동일합니다).
.then(addExclamationMark)
.then(addExclamationMark)
.then(addExclamationMark)
.then((output) => console.log(output))
.catch((error) => console.log(error))
먼저 'addExclamationMark' 정의 앞에 'async' 키워드를 추가합니다. 그 다음, 핵심 연산을 언래핑하기 위한 새로운 'Promise' 객체의 구조를 제거합니다. 그 다음에는 'resolve'와 'reject'의 호출을 각각 'return'과 'throw' 문으로 대체합니다.
async 함수 addExclamationMark(input) {
만약 (typeof input === "string") {
const output = input + "!"
출력 반환
} 그렇지 않으면 {
const error = 새로운 오류("Invalid Input")
던지기 오류
}
}
비동기 함수 main() {
시도해 {
const output1 = waitit addExclamationMark("안녕, 세상")
const output2 = wait, addExclamationMark(output1)
const output3 = wait, addExclamationMark(output2)
const output4 = wait, addExclamationMark(output3)
const output5 = wait, addExclamationMark(output4)
console.log(output5)
} 잡기 (오류) {
console.log(오류)
}
}
main()
약속 체인을 대체하기 위해 'addExclamationMark'의 다섯 개 호출을 완전히 분리합니다. 'addExclamationMark'의 각 호출 앞에 'wait'를 배치하여 출력을 새로운 상수에 할당합니다. 또한 이전 호출 'addExclamationMark'의 출력을 다음 호출 인수로 전달합니다. 마지막으로 'console.log(output5)'를 호출하여 최종 출력을 표시합니다.
이전에 'catch' 메서드로 랩 처리했던 오류 처리 연산들은 이제 'catch' 블록에 넣습니다. 'catch' 블록은 'try' 블록으로, 아무 문제가 없을 때 수행하고자 하는 모든 연산과 'addExclamationMark'의 다섯 번의 호출을 포함합니다.
두 세계의 장점을 모두 누릴 수 있어
'async/waitit'를 완전히 사용하고 싶지 않은 분들을 위해 좋은 소식을 전합니다: 두 가지 장점을 모두 누릴 수 있습니다! 'async'가 비동기 함수('Promise' 객체를 반환하는 함수)를 정의하는 더 나은 방법이라는 생각에 확신이 있다고 가정해 봅시다. 하지만 Promise Chaining을 사용할 자유를 포기하고 싶지 않습니다. 마지못해 'await' 키워드로 코드베이스를 가득 채우는 대신, 'async'를 Promise Chaining과 함께 사용할 수 있습니다. 원하는 스타일이라면 'wait'와 약속 체인을 섞어도 됩니다. 세상은 여러분의 무대입니다!
이 글에서는 비동기 프로그래밍에서 연산의 순차적 실행을 조직하는 일반적인 방식(약속 체인)에 대한 강력한 대안인 'async/wait'의 기본을 제시했습니다. 먼저, 'async/wait' 사용법과 사용 조건을 설명했습니다. 그 다음, 'async/wait'로 비동기 코드를 다시 작성하는 방법을 시연했습니다. 마지막으로, 절대적인 약속 없이 'async'와 'wait'를 따로, 그리고 선택적으로 사용하는 것이 완전히 허용된다고 언급했습니다. 이 글이 여러분께 'async'와 'wait'를 사용하기 시작하거나 적어도 고려해보도록 영감을 주셨길 바랍니다.