- Published on
자바스크립트 Promise 직접 구현해보기
이 글은 "커맨드 패턴으로 Promise를 만들기"의 선행 단계인 글입니다. 커맨드 패턴을 적용하기 전에 Promise에 대해 간단한 이해가 필요합니다.
글 작성 목적
- Promise에 대한 기본적인 이해
- 이해를 기반으로 직접 만들어보기
예상 독자
- 자바스크립트의 Promise를 사용해봤지만 더 깊게 알고 싶은 자바스크립트 입문자
이 글은 Promise의 기본 개념은 설명하지 않는 부분들이 있습니다. 미리 알고 있다면 좋고, 그렇지 않다면 MDN 문서의 Promise 내장 객체 설명을 보고 읽는 것을 추천합니다. 이 글에서는 Promise의
.resolve()와.then()메서드만 다룹니다. 추가로reject(),.catch()는 다루지 않는 점을 참고해주세요.
Promise 객체를 생성하고 출력해보는 간단한 예제를 소개하고, 직접 만든 나만의 Promise 클래스를 사용하여 예제를 실행하여 같은 동작을 하는지 확인해보겠습니다.
Promise 생성자의 구조와 타입 알아보기
Promise 생성자와 그 구조
MDN 문서의 Promise 생성자 설명의 설명을 발췌했습니다.
Promise 생성자는 주로 프로미스를 지원하지 않는 함수를 감쌀 때 사용합니다.
Promise 생성자는 executor를 매개변수로 받습니다.
new Promise(executor)
Promise 생성자의 타입
Promise의 타입에 대해 알아봅시다. 타입스크립트에서 Promise 객체를 생성하면서 Promise 객체의 타입을 확인해봤습니다.
var Promise: PromiseConstructor
new <unknown>(executor: (resolve: (value: unknown) => void, reject: (reason?: any) => void) => void) => Promise<unknown>
네스팅 구조가 복잡하니 보기 좋게 정리해보았습니다.
var Promise: PromiseConstructor
new <unknown>(
executor: (
resolve: (value: unknown) => void,
reject: (reason?: any) => void
) => void
) => Promise<unknown>
executor는 resolve 및 reject 인수를 전달할 실행 함수입니다. 참고로 글 초반에 언급했듯이 이 글에서는 reject()는 사용하지 않고 resolve() 인자만 사용할 것입니다.
만들고자 하는 것
위에서 알아본 Promise 생성자의 명세에 따라 1초 후에 "Success!"를 저장하는 Promise 객체를 생성하는 함수를 만들겠습니다.
const successAfter1SecondPromise = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve('Success!')
}, 1000)
})
}
이제 만든 함수를 실행해서 콘솔에 출력해봅니다.
successAfter1SecondPromise().then((value) => console.log('Result: ', value))
// Output: "Result: ", "Success!"
TypeScript Playground에서도, 개발자 도구 콘솔에서도 잘 동작하는 것을 확인할 수 있습니다.
Promise를 직접 만들기
Promise 생성자를 직접 만들어보겠습니다. 이름은 MyPromise로 해보겠습니다. 코드에 대한 설명은 주석으로 첨부했습니다.
type ResolveFunction<T> = (value?: T) => void
class MyPromise<T> {
private resolveCallback: ResolveFunction<T> | null = null // resolve 콜백 함수를 저장하는 변수
private settled: boolean = false // Promise가 이미 처리되었는지 여부를 나타내는 변수
private settledValue?: T // 처리된 Promise의 결과 값을 저장하는 변수
constructor(executor: (resolve: ResolveFunction<T>) => void) {
// resolve 함수를 받아 비동기 작업을 수행하는 생성자 메서드
const resolve = (value: T) => {
// 내부 resolve 함수 정의: 비동기 작업이 완료되었을 때 호출되어 처리 상태를 변경함
if (!this.settled) {
// 이미 처리된 Promise인 경우 실행하지 않음
this.settled = true // 처리 상태를 완료로 변경
this.settledValue = value // 처리된 Promise의 결과 값을 설정
// resolveCallback이 설정되어 있다면 실행
if (this.resolveCallback) {
this.resolveCallback(value)
}
}
}
try {
executor(resolve) // executor 함수 실행: 비동기 작업을 수행
} catch (error) {
// executor 함수 실행 중 에러가 발생한 경우 처리
this.settled = true // 처리 상태를 완료로 변경
throw error // 에러를 다시 던져서 처리 상태를 완료 상태로 설정함
}
}
then<U>(onFulfilled: (value: T) => U): MyPromise<U> {
// then 메서드: Promise가 성공했을 때의 처리를 정의함
return new MyPromise<U>((resolve) => {
// 새로운 Promise 객체를 반환하고, resolve 함수를 받아 처리를 정의함
if (this.settled) {
// 이미 처리된 Promise인 경우 처리 결과를 바로 반환
resolve(onFulfilled(this.settledValue!))
} else {
// 아직 처리되지 않은 Promise인 경우 resolveCallback을 설정하여 비동기 작업 완료 후 처리함
this.resolveCallback = (value: T) => {
resolve(onFulfilled(value))
}
}
})
}
}
만들고자 하는 것을 직접 만든 Promise로 테스트 해보기
TypeScript Playground에서 간단히 테스트할 수 있습니다.
/* class MyPromise<T> {
* ...위에서 작성한 MyPromise 코드
* }
*/
// MyPromise를 사용해서 successAfter1SecondPromise 선언
const successAfter1SecondPromise = () => {
return new MyPromise((resolve) => {
setTimeout(() => {
resolve('Success!')
}, 1000)
})
}
// 실행
successAfter1SecondPromise().then((value) => console.log('Result: ', value))
// Output: "Result: ", "Success!"
동일한 결과가 출력되는 것을 확인할 수 있습니다.
전체 코드를 TypeScript Playground에 작성해두었습니다. [링크]
해당 링크에서 테스트가 가능합니다.