일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- EventListener
- 개발
- 백준
- 프로그래머스
- State
- memory
- Hook
- deep
- BOJ
- Java
- axios
- javascript
- 원리
- react
- virtual Dom
- 프로젝트
- API
- 상태
- 가상 DOM
- 요청
- LeetCode
- 유용한 사이트
- 상태 끌어올리기
- DoM
- Dive
- programmers
- 꿀팁
- Today
- Total
탄탄한 기본기!
Promise 내부 구현 따라해보기 본문
자바스크립트에는 Promise라는 아주 중요한 객체가 있다. 내부적으로 비동기적인 로직을 처리할 때 이 Promise객체를 통해서 callback hell이나 에러 처리 등 여러 가지 문제점들을 해결해주고 있다.
// callback hell
get('/step1', a => {
get(`/step2/${a}`, b => {
get(`/step3/${b}`, c => {
get(`/step4/${c}`, d => {
console.log(d);
});
});
});
});
이러한 코드를 Promise를 사용해 바꾸면 다음과 같다.
promiseGet(`/step/1`)
.then(a => promiseGet(`/step/${a}`))
.then(b => promiseGet(`/step/${b}`))
.then(c => promiseGet(`/step/${c}`))
.catch(err => console.error(err));
코드도 훨씬 간결하며 보기도 좋다. 그럼 이러한 Promise 객체를 사용하는 법은 어떻게 될까? 간단한 예시로 setTimeout을 통해서 2초 후 숫자를 resolve해주는 프로미스 객체를 만들어 보도록 하겠다.
const numberPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(10);
}, 2000);
});
console.log(numberPromise); // {state: "PENDING", ...}
즉 프로미스는 콜백함수를 인수로 넘겨주고, 콜백 함수에 주입받는 첫 번째와 두 번째 인수로 각각 (비동기) 처리를 성공했을 경우와 실패했을 경우에 실행할 콜백 함수를 넘겨받는다. 그 후 후, 조건에 맞도록 resolve(첫 번째 매개변수) 또는 reject(두 번째 매개변수)를 실행하는 것이다.
본인의 경우에는 callback함수가 callback함수를 받아 사용하고 인수를 넘겨주고 하는 것이 매우 복잡해보여서 처음에 이해하는데 꽤 오래 걸렸다. 하지만 Promise가 어떤 방식으로 동작하는지 알아보기 위해서 직접 (전혀 안 똑같겠지만) 한번 비슷(?)하게라도 동작하도록 직접 구현해보았다.
function myPromise(callback) {
this.state = 'PENDING';
this.value;
}
일단 이렇게 상태와 resolve할 값 두 가지 상태를 가진다고 생각하였다.
그리고 전달받은 callback함수에 인수를 두 개 넘겨주어야 하는데, 바로 reslover와 rejector 함수이다.
function myPromise(callback) {
this.state = 'PENDING';
this.value;
const resolver = value => {
if (this.state !== 'PENDING') return;
this.state = 'fullfilled';
this.value = value;
};
const rejector = value => {
if (this.state !== 'PENDING') return;
this.state = 'rejected';
this.value = value;
};
}
이 함수는 Promise를 생성할 때 넘겨주는 콜백 함수 내부에서 호출되는데, 이때 값을 하나 넘겨받기 때문에 value라는 매개변수를 통해서 이를 지정해주었다.
그리고 이 함수들을 화살표 함수로 작성한 이유는, 나중에 전달받은 callback을 실행시킬 때 this 바인딩을 자신의 상위 스코프의 this로 바인딩해주기 위해서이다. 만약 함수 표현식이나 선언문으로 작성했다면 Function.prototype.bind 메서드로 this를 바인딩해주어야 하는 번거로움이 있었을 것이다.
그리고 이 후, 넘겨받은 callback에 resolver와 rejector를 넘겨주며 실행한다.
function myPromise(callback) {
this.state = 'PENDING';
this.value;
const resolver = value => {
if (this.state !== 'PENDING') return;
this.state = 'fullfilled';
this.value = value;
};
const rejector = value => {
if (this.state !== 'PENDING') return;
this.state = 'rejected';
this.value = value;
};
callback(resolver, rejector);
}
그리고 여기까지 잘 되었는지 테스트를 해보겠다. 테스트는 알아보기 쉽도록 브라우저 콘솔을 활용했다.
...
const myProm = new myPromise((resolve, reject) => {
setTimeout(() => {
resolve('RESOLVED');
}, 2000);
});
그럼 위 그림과 같이 여기까지는 의도한 대로 잘 동작하는 것을 확인할 수 있다. 그럼 이제 Promise의 then 메서드까지 한번 비슷(?)하게 동작하도록 간단하게 정도만 구현해보겠다.
먼저, then이라는 함수는 Promise 생성자 함수가 평가될 때 실행되는 것이 아니라 비동기적으로 resolver가 동작한 이후에, 즉 상태가 "fullfilled"로 변환된 이후에 호출되는 것이기 때문에 변수를 따로 두어 함수 값을 가질 수 있도록 하였다.
function myPromise(callback) {
this.state = 'PENDING';
this.value;
this.thenFunc;
this.then = thenCallback => {
this.thenFunc = thenCallback;
};
const resolver = value => {
if (this.state !== 'PENDING') return;
this.state = 'fullfilled';
this.value = value;
};
const rejector = value => {
if (this.state !== 'PENDING') return;
this.state = 'rejected';
this.value = value;
};
callback(resolver, rejector);
}
그리고, 상태가 "fullfilled"로 변화했을 때, 즉 resolver가 호출이 되었을 때 then 메서드가 동작하도록 비동적인 로직을 구현해야 했으므로 여기에서는 resolver가 호출이 되었을 때 thenFunc를 호출할 수 있도록 해주었다.
then에 넘겨주는 콜백 함수는 매개변수로 reslove된 값을 하나 넘겨받기 때문에(then 함수의 첫 번째 인수로 relove 되었을 때 실행할 함수, 두 번째 인수로 reject 되었을 때 실행할 함수 총 2개의 인수를 넘겨준다.) 넘겨받은 콜백 함수의 인수에 this.value를 넘겨줄 수 있도록 하였다.
function myPromise(callback) {
this.state = 'PENDING';
this.value;
this.thenFunc;
this.catchFunc;
this.then = (resolveCallback, rejectCallback) => {
this.thenFunc = resolveCallback;
this.catchFunc = rejectCallback;
};
const resolver = value => {
if (this.state !== 'PENDING') return;
this.state = 'fullfilled';
this.value = value;
this.thenFunc(this.value);
};
const rejector = value => {
if (this.state !== 'PENDING') return;
this.state = 'rejected';
this.value = value; // reason
this.catchFunc(this.value); // reason
};
callback(resolver, rejector);
}
그리고 이제 아까 했던 테스트에 then까지 함께 테스트하면 다음과 같다.
Promise를 조금 더 잘 이해하기 위해서 이런 방식으로 실제 Promise와 비슷하게 동작하는 나만의 Promise 객체를 한 번 만들어 보았다.
하지만 myPromise의 경우에는 문제점이 있는데, new myPromise로 생성한 객체에 then 메서드를 호출하지 않을 경우에는 오류 메세지가 발생하게 된다. 왜냐하면 resolve가 될 때 함께 thenFunc를 호출하는데, then 메서드가 호출되지 않았다면 thenFunc가 undefined이기 때문이다.
이러한 문제를 바로잡기 위해서는 thenFunc를 resolve가 호출할 때 상태를 체크해서 예외 처리를 해주거나, thenFunc의 기본 함수를 지정해 예외를 처리하는 방식을 사용하면 될 것이다.
이 부분에 대해서는 Promise implementation을 키워드로 검색을 통하여 내부적으로 어떻게 동작하는지에 대해 더 자세하게 학습한 후 다시 구현해볼 예정이다.
'개인 공부 > JS (자바스크립트)' 카테고리의 다른 글
메모리 관리와 removeEventListener의 진실 (Garbage collection) (0) | 2022.01.28 |
---|---|
Async/Await 잘 활용하는 법 (0) | 2021.07.28 |
javaScript - Ajax (0) | 2021.06.27 |
javaScript - 디바운스(debounce), 스로틀(throttle) (0) | 2021.06.10 |
DOM(Document Object Model) - HTMLCollection과 NodeList (0) | 2021.06.06 |