일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 자바스크립트
- 자바스크립트로 달력만들기
- react search input
- 자바스크립트 검색 구현하기
- debound
- react 배열 재정렬하기
- 올리브영 발색비교 기능구현하기
- 유저접근제한
- new Promise()
- JavaScript
- 자바스크립트로 하는 자료 구조와 알고리즘
- 리액트 이미지 미리보기
- 리액트 dragdrap
- 프로미스 직접 구현하기
- 리액트 고차컴포넌트
- next seo
- compose 함수
- search input
- range 함수 직접 만들기
- 프로미스
- 넥스트 검색엔진최적화
- 코어자바스트립트
- 자바스크립트 reduce 함수 직접 만들어보기
- 러닝리액트
- 리엑트 검색 기능 구현하기
- react-beautiful-dnd
- 검색 자동완성
- 순수함수
- 초집합
- 노드교과서
- Today
- Total
미주알고주알
[Javascript] Promise 객체 직접 구현하기 본문
비동기 처리라 하면 가장 먼저 떠오르는 `Promise`.
생성자 함수로써 프로미스 객체를 만들기 때문에
이 생성자란 개념을 이용해 프로미스를 직접 구현해보고자 한다.
프로미스 객체는 `then`, `catch`란 메소드를 갖고 있고, 인스턴스 객체라면 모두 공유되는 메소드이기 때문에, 상속의 개념인 `prototype`이란 아이디어를 같이 사용해보면 좋을 것 같다.
또한 프로미스의 인자로 들어오는 함수를 실행할 때 그 함수의 매개변수에 `resolve`, `reject`의 메소드가 들어와야 하며,
`resolve`, `reject`에 담겨져 오는 성공 및 에러 값은 `then`, `catch`메소드가 실행 후 넘겨져 나온다.
이런 프로미스의 구조 및 원리를 생각하면서
다음과 같은 코드를 짜봤다.
function Promise(cb) {
Promise.prototype.then = tcb => {
Promise.prototype.thenFn = tcb;
return this;
};
Promise.prototype.catch = ccb => {
Promise.prototype.catchFn = ccb;
return this;
};
const resolve = succ => {
this.state = 'resolve';
this.thenFn(succ);
};
const reject = err => {
this.state = 'reject';
if (this.catchFn) this.catchFn(err);
};
cb(resolve, reject);
if (new.target) this.state = 'pending';
}
여기서 사용된 코드를 이해하기 위해선,
1. 생성자 함수
2. prototype 상속
3. thisBinding
과 같은 개념을 다시 한 번 살펴봐야 한다.
그런데 위 코드는 뭔가 아쉽다. 다음과 같은 문제가 해결되지 않았기 때문이다.
1. `finally` 메소드 없음
2. 여러개의 then, finally 메소드가 실행될 수 있고, finally는 then과 catch가 모두 실행된 후, 가장 마지막으로 실행되어야 한다.
(순서를 보장해야 함.)
3. `reject`의 인자는 `then`와 `catch`에 모두 들어올 수 있다. (실제 promise 객체의 then 메소드에는 성공 객체를 첫번째 인자로, 실패 객체를 두번째 인자로 하는 콜백이 들어온다.)
4.`catch` 발생 시 `finally`로 바로 이동한다.
이를 해결하기 위해, 기존의 `catch`와 `then`과 동일하게 `prototype` 상속 메소드로서 `finally`를 추가하였다.
`finally`의 콜백은 모든`then`과 `catch` 이후에 실행돼야 하기 때문에 순.서.를 보장하기 위해 각각 배열에 담았다.
이후 `then` 배열이 모두 끝났을 때 `finally` 배열이 실행될 수 있도록 재귀 함수를 통해 `then` 배열에 남은 콜백가 없을 때까지 현재 콜백에 성공 객체를 전달하였다. 만약에 이 성공 객체가 또 하나의 프로미스 객체라면 `resolve` 상태 객체로 변환시켜 반환한다. 왜냐하면 `then`는 프로미스를 반환하면서 연쇄적으로 `then`을 요청 할 수 있기 때문이다. 끝으로, 배열에 남은 콜백에 없다면 `finally` 배열의 요소들을 하나씩 실행하였다.
또한 에러 객체와 관련해서, `catch`메소드가 이미 실행되었다면 그 메소드로 실패 객체를 반환하고 그렇지 않으면 기존의 `then` 메소드 안으로 전달하는 방식으로 수정하였다.
function Promise(cb) {
const thenFns = [];
const finalFns = [];
Promise.prototype.then = tcb => {
if (typeof tcb === 'function') thenFns.push(tcb);
return this;
};
Promise.prototype.catch = ccb => {
if (!Promise.prototype.catchFn) Promise.prototype.catchFn = ccb;
return this;
};
Promise.prototype.finally = fcb => {
if (typeof fcb === 'function') finalFns.push(fcb);
return this;
};
const finalRunner = () => {
for (const ffn of finalFns) ffn();
};
const resolve = succ => {
const recur = preRet => {
const fn = thenFns.shift();
if (!fn) {
this.state = 'resolve';
return finalRunner();
}
if (preRet instanceof Promise) {
preRet.then(fn).then(res => {
recur(res);
});
} else {
recur(fn(preRet));
}
};
recur(succ);
};
const reject = err => {
this.state = 'reject';
if (this.catchFn) this.catchFn(err);
finalRunner();
};
cb(resolve, reject);
if (new.target) this.state = 'pending';
}
이 코드는 콘솔 상에서 어떻게 나오는지 확인해보자.
이 테스트 코드를 활용해서! (간단히 확인하기 위해, `jest` 말고 커스텀으로 만든 코드를 사용했다.)
const randTime = val => {
return new Promise(resolve => setTimeout(resolve, Math.random() * 1000, val));
};
const p = new Promise((resolve, reject) => {
setTimeout(() => {
const now = Date.now();
if (now % 2 === 0) resolve(now);
else reject(new Error('에러 발생!!!!'));
}, 1000);
});
p.then(res => {
console.log('p.then.res1>>>>', res);
return randTime(1);
})
.then(res => {
console.log('p.then.res4>>>', res);
return randTime(2);
})
.then(res => {
console.log('p.then.res2>>>', res);
return 'finally';
})
.then(console.log('p.then.res3!!!'))
.then(res => res || 'TTT')
.catch(err => console.error('err-1>>', err))
.catch(err => console.error('err-2>>', err))
.finally(() => console.log('finally-1'))
.finally(() => console.log('finally-2'));
프로미스, 자바스크립트에서 손에 꼽히게 중요한 개념인데,
이렇게 직접 만들어보지 않는 이상 그 원리가 어떠한지를 제대로 알 길이 없다.
이렇게 코드를 짜면서 몇번이고 머리를 움켜 쥐었지만 너무나 귀한 시간이었다. 언제 한 객체를 이렇게 물고 늘어져 보겠낭..
추후에 수정 사항이 있으면 계속적으로 고쳐나갈 예정이다.
'Javascript' 카테고리의 다른 글
[Javascript] new Set 객체 활용하기(교집합, 합집합, 차집합, 초집합) (0) | 2023.03.24 |
---|---|
[Javascript] iterator을 활용해 지하철 노선도 만들기 (1) | 2023.03.24 |
[Javascript] new Date 객체로 달력 만들어보기 (0) | 2023.03.24 |
[Javascript] Promise.all 메소드 직접 구현하기 (0) | 2023.03.24 |
[Javascript] 클로져 (0) | 2023.03.06 |