詳細検索

Async/Await: 약속 체인을 싫어하는 이들을 위한 게임 체인저

아바타
글쓴이 Chen Ziyu

Async/Await: 약속 체인을 싫어하는 이들을 위한 게임 체인저
English에서 번역 • 원문 보기

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'를 사용하기 시작하거나 적어도 고려해보도록 영감을 주셨길 바랍니다.

Related Articles