JavaScript Promise 구현하기
Promise 기본 동작 살펴보기
Promise 객체는 다음과 같은 세 가지의 상태를 지닐 수 있다.
- pending
- fulfilled
- rejected
최초로 Promise 객체가 생성되면 pending
상태가 된다. resolve되면 fulfilled
상태가 되고, 반대로 reject
되면 rejected 상태가 된다.
조금 더 자세히, 아래와 같이 정리할 수 있다.
- Promise는 최초로 생성되었을 시점에
pending
상태이다. - Promise가 v라는 값으로 resolve 되었다면, Promise는
fulfilled
상태가 되며, v를 fulfillment value라고 부른다. - Promise가 e라는 에러로 reject 되었다면, Promise는
rejected
상태가 되며, e를 rejection value라고 부른다.
Promise 객체의 처리 흐름은 위와 같다. 위 그림에서 주의 깊게 볼 부분은 다음과 같다.
then()
과 catch()
가 리턴될 때 Pending이라는 초기 상태를 갖는 Promise 객체를 반환한다. 이를 통해 Promise Chaining이 가능해진다는 점이다.
Promise에서 사용되는 resolve()
, reject()
, then()
, catch()
이 네 메서드를 직접 구현함으로써 Promise 클래스와 유사한 동작을 하도록 구현할 수 있다.
직접 구현해보기
const PromiseStatus = {
PENDING: "pending",
FULFILLED: "fulfilled",
REJECTED: "rejected",
};
const addToTaskQueue = callback => setTimeout(callback, 0);
const addAllToTaskQueue = fulfilledQueue => fulfilledQueue.forEach(callback => addToTaskQueue(callback));
PromiseStatus
: Promise의 상태를 표기하기 위한 객체이다.
addToTaskQueue
: 태스크 큐에 콜백 함수를 추가해주는 역할을 한다.
addAllToTaskQueue
: fulfilledQueue에 위치하는 모든 콜백 함수들을 차례로 꺼내, 태스크 큐에 넣어주는 역할을 한다.
class MyPromise {
constructor(callback) {
this.status = PromiseStatus.PENDING;
this.result = undefined;
this.fulfilledQueue = [];
this.rejectedQueue = [];
callback(this.resolve.bind(this), this.reject.bind(this));
}
...
}
const PromiseStatus = {
PENDING: "pending",
FULFILLED: "fulfilled",
REJECTED: "rejected",
};
const addToTaskQueue = callback => setTimeout(callback, 0);
const addAllToTaskQueue = fulfilledQueue => fulfilledQueue.forEach(callback => addToTaskQueue(callback));
PromiseStatus
: Promise의 상태를 표기하기 위한 객체이다.addToTaskQueue
: 태스크 큐에 콜백 함수를 추가해주는 역할을 한다.addAllToTaskQueue
: fulfilledQueue에 위치하는 모든 콜백 함수들을 차례로 꺼내, 태스크 큐에 넣어주는 역할을 한다.class MyPromise {
constructor(callback) {
this.status = PromiseStatus.PENDING;
this.result = undefined;
this.fulfilledQueue = [];
this.rejectedQueue = [];
callback(this.resolve.bind(this), this.reject.bind(this));
}
...
}
다음으로 MyPromise
클래스를 만든 뒤, 위와 같이 생성자를 작성한다.
- status : Promise 객체의 초기 상태 Pending을 지닌다.
- result : Promise 객체가 resolve 될 때의 fulfillment value 값을 의미한다.
- fulfilledQueue : Promise가 이행될 때 호출되는 콜백 함수들이 위치하는 큐이다.
- rejectedQueue : Promise가 거부될 때 호출되는 콜백 함수들이 위치하는 큐이다.
- Promise 생성자 호출시 전달되는
callback
내부에서 resolve(), reject() 호출시, 프로미스 객체에 대한 정보를 넘겨주기 위해bind
를 사용한다.
class MyPromise {
...
then(onFulfilled) {
switch (this.status) {
case PromiseStatus.PENDING:
return new MyPromise(resolve => {
this.fulfilledQueue.push(() => resolve(onFulfilled(this.result)));
});
case PromiseStatus.FULFILLED:
return new MyPromise(resolve => resolve(onFulfilled(this.result)));
}
return this;
}
...
}
then
메서드 구현부는 위와 같다. then() 호출시 파라미터(콜백 함수)를 onFulfilled
로 받는다.
- 프로미스 객체의 상태 값이
Pending
이면, 새로운 MyPromise 객체를 만들며, 동시에 fulfilledQueue에 콜백 함수들을 넣어둔다. - 프로미스 객체의 상태 값이
Fulfilled
이면, 새로운 MyPromise 객체를 만들어 리턴한다.
class MyPromise {
...
catch(onRejected) {
const rejectedTask = () => {
onRejected(this.result);
};
switch (this.status) {
case PromiseStatus.PENDING:
this.rejectedFunc = rejectedTask;
break;
case PromiseStatus.REJECTED:
addToTaskQueue(rejectedTask);
}
}
...
}
catch
메서드 구현부는 위와 같다. 거부될 때 실행할 콜백 함수 onRejected를 파라미터로 받으며, 프로미스 객체 상태가 Rejected일 때 태스크 큐에 콜백을 넣어준다.
class MyPromise {
...
resolve(val) {
if (this.status !== PromiseStatus.PENDING) {
return this;
}
this.status = PromiseStatus.FULFILLED;
this.result = val;
addAllToTaskQueue(this.fulfilledQueue);
}
...
}
resolve
메서드 내부에선 다음과 같이 동작한다.
- 프로미스 객체 상태를 Fulfilled로 변경한다.
- 파라미터로 전달된 val을 result에 담는다.
- 이행 시 실행할 함수들이 담긴
fulfilledQueue
의 모든 함수들을 꺼내 태스크 큐에 넣어준다.
class MyPromise {
...
reject(error) {
if (this.status !== PromiseStatus.PENDING) {
return this;
}
this.status = PromiseStatus.REJECTED;
this.result = error;
addToTaskQueue(this.rejectedFunc);
}
}
reject
메서드 동작은 resolve와 유사하다.
문제점
Promise 깊이가 깊어지니 제대로 작동하지 않는다.
new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("First");
}, 1000);
})
.then(res => {
console.log(res); // First
return new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("Second");
}, 2000);
});
})
.then(res => {
console.log(res); // MyPromise { status: 'pending', result: undefined, ... }
});
위처럼 then 안에서 새로운 Promise 객체를 리턴하게 되는 경우, 다음에 받는 then에서 객체 그 자체가 출력된다.
해결 방법
포스트를 참고해서 위의 문제점을 해결할 수 있었다.
resolve(value) {
if (this.status !== PromiseStatus.PENDING) {
return this;
}
if (value instanceof MyPromise) {
value.then(innerPromiseValue => {
this.status = PromiseStatus.FULFILLED;
this.result = innerPromiseValue;
addAllToTaskQueue(this.fulfilledQueue);
});
} else {
this.status = PromiseStatus.FULFILLED;
this.result = value;
addAllToTaskQueue(this.fulfilledQueue);
}
}
resolve
메서드의 파라미터가 프로미스 객체라면, 내부에서 then()
을 호출시킴으로써, 실행시킨 프로미스 객체 내부의 result를 참조할 수 있게 되었다.
전체 코드
const PromiseStatus = {
PENDING: "pending",
FULFILLED: "fulfilled",
REJECTED: "rejected",
};
const addToTaskQueue = callback => setTimeout(callback, 0);
const addAllToTaskQueue = fulfilledQueue => fulfilledQueue.forEach(callback => addToTaskQueue(callback));
class MyPromise {
constructor(callback) {
this.status = PromiseStatus.PENDING;
this.result = undefined;
this.fulfilledQueue = [];
this.rejectedQueue = [];
callback(this.resolve.bind(this), this.reject.bind(this));
}
then(onFulfilled) {
switch (this.status) {
case PromiseStatus.PENDING:
return new MyPromise(resolve => {
this.fulfilledQueue.push(() => resolve(onFulfilled(this.result)));
});
case PromiseStatus.FULFILLED:
return new MyPromise(resolve => resolve(onFulfilled(this.result)));
}
return this;
}
catch(onRejected) {
const rejectedTask = () => {
onRejected(this.result);
};
switch (this.status) {
case PromiseStatus.PENDING:
this.rejectedFunc = rejectedTask;
break;
case PromiseStatus.REJECTED:
addToTaskQueue(rejectedTask);
}
}
resolve(value) {
if (this.status !== PromiseStatus.PENDING) {
return this;
}
if (value instanceof MyPromise) {
value.then(innerPromiseValue => {
this.status = PromiseStatus.FULFILLED;
this.result = innerPromiseValue;
addAllToTaskQueue(this.fulfilledQueue);
});
} else {
this.status = PromiseStatus.FULFILLED;
this.result = value;
addAllToTaskQueue(this.fulfilledQueue);
}
}
reject(error) {
if (this.status !== PromiseStatus.PENDING) {
return this;
}
this.status = PromiseStatus.REJECTED;
this.result = error;
addToTaskQueue(this.rejectedFunc);
}
}
new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("First");
}, 1000);
})
.then(res => {
console.log(res);
return new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("Second");
}, 2000);
});
})
.then(res => {
console.log(res);
});
Reference
- https://www.getoutsidedoor.com/2020/03/12/promise-구현해보며-원리-살펴보기/
- https://p-iknow.netlify.app/js/custom-promise
- https://velog.io/@teihong93/Promise-구현하기-1
Author And Source
이 문제에 관하여(JavaScript Promise 구현하기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://velog.io/@ddb8036631/JavaScript-Promise-구현하기
저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
const PromiseStatus = {
PENDING: "pending",
FULFILLED: "fulfilled",
REJECTED: "rejected",
};
const addToTaskQueue = callback => setTimeout(callback, 0);
const addAllToTaskQueue = fulfilledQueue => fulfilledQueue.forEach(callback => addToTaskQueue(callback));
class MyPromise {
constructor(callback) {
this.status = PromiseStatus.PENDING;
this.result = undefined;
this.fulfilledQueue = [];
this.rejectedQueue = [];
callback(this.resolve.bind(this), this.reject.bind(this));
}
then(onFulfilled) {
switch (this.status) {
case PromiseStatus.PENDING:
return new MyPromise(resolve => {
this.fulfilledQueue.push(() => resolve(onFulfilled(this.result)));
});
case PromiseStatus.FULFILLED:
return new MyPromise(resolve => resolve(onFulfilled(this.result)));
}
return this;
}
catch(onRejected) {
const rejectedTask = () => {
onRejected(this.result);
};
switch (this.status) {
case PromiseStatus.PENDING:
this.rejectedFunc = rejectedTask;
break;
case PromiseStatus.REJECTED:
addToTaskQueue(rejectedTask);
}
}
resolve(value) {
if (this.status !== PromiseStatus.PENDING) {
return this;
}
if (value instanceof MyPromise) {
value.then(innerPromiseValue => {
this.status = PromiseStatus.FULFILLED;
this.result = innerPromiseValue;
addAllToTaskQueue(this.fulfilledQueue);
});
} else {
this.status = PromiseStatus.FULFILLED;
this.result = value;
addAllToTaskQueue(this.fulfilledQueue);
}
}
reject(error) {
if (this.status !== PromiseStatus.PENDING) {
return this;
}
this.status = PromiseStatus.REJECTED;
this.result = error;
addToTaskQueue(this.rejectedFunc);
}
}
new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("First");
}, 1000);
})
.then(res => {
console.log(res);
return new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve("Second");
}, 2000);
});
})
.then(res => {
console.log(res);
});
- https://www.getoutsidedoor.com/2020/03/12/promise-구현해보며-원리-살펴보기/
- https://p-iknow.netlify.app/js/custom-promise
- https://velog.io/@teihong93/Promise-구현하기-1
Author And Source
이 문제에 관하여(JavaScript Promise 구현하기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@ddb8036631/JavaScript-Promise-구현하기저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)