
나에게 넘나 어려운 비동기.. 왜 굳이 굳이 promise를 사용해서 비동기로 작업해야 하는지에 대해 궁금증이 항상 가득했었다. 물음표 살인마인 나에게는 넘나 고통스러운 것. 그러던 와중에 강같은 글을 발견해서, 그 글을 보고 내 방식대로 다시 정리해보았다.
(출처: https://springfall.cc/article/2022-11/easy-promise-async-await)
Promise 란?
: 프로미스 객체는 생산자와 소비자를 연결해준다.
예시)
⇒ 커피 주문이 오면 프로미스 객체에 넣어준다.
⇒ 프로미스 객체에서 커피를 만들어서 보관한다.
⇒ 소비자에서 .then()을 하는데 이게 바로 호출되는 게 아니라, 프로미스가 소비자에게 커피가 다 만들어졌다는 것을 알려줘야 .then에 전달된 콜백함수가 실행된다.
.then으로 체이닝해서 순서대로 비동기 작업을 실행할거면, 동기적으로 작업하면 되지 왜 굳~~~이 비동기로 작업해야 하는 이유
⇒ 동기적으로 작업하면 1개의 작업을 하는 동안 나머지 작업을 못한다. 하지만 비동기적으로 작업을 하면 외부로부터 데이터를 읽어오는, 즉 오랜 시간이 걸리는 작업을 하는 동안 비동기로 체이닝한 작업을 실행할 수 있기 때문에 굳~~~이 비동기 작업으로 하는 것.
Promise 기본 사용법
- promise = 비동기 작업의 단위
- Promise로 관리할 비동기 작업을 만드는 정석적인 방법은
new Promise()이다.new Promise()를 통해 새로운 Promise 객체를 생성했고, 이 Promise 생성자는 특별한 콜백 함수(executor)를 인자로 받는다.- executor는 인수로
resolve, reject를 전달받는다. resolve: executor 내에서 호출할 수 있는 또 다른 함수.resolve를 호출하게 된다면 “이 비동기 작업이 성공했어!”를 의미함.reject: executor 내에서 호출할 수 있는 또 다른 함수.reject를 호출하게 된다면 “이 비동기 작업이 실패했서,,, ㅜㅜ”를 의미함.- ⇒ 쨌든 여기까지 promise1이라는 Promise 객체를 얻었음.
const promise1 = new Promise((resolve, reject) => { // 비동기로 처리할 작업 });
Promise의 특징
new Promise()를 하는 순간, 이 안에 작성한 비동기 작업은 바로 시작된다. 즉, 그냥 기다리지 않고 바로 생성자 안의 코드를 실행함.- 비동기 작업이 끝난 후 작업이 성공하거나 실패했을 때의 뒷처리를 해줘야 한다. Promise가 끝나고 난 다음의 동작을 우리가 설정해줄 수 있음. ⇒
then,catchthen: 해당 Promise가 성공했을 때(resolve)의 동작을 지정한다. 인자로 함수를 받음.catch: 해당 Promise가 실패했을 때(reject)의 동작을 지정한다. 인자로 함수를 받음.
⇒ 위 메소드들은 체인 형태로 활용할 수 있다. (=연속적으로 호출할 수 있음)
```jsx
// 1. promise1 내에서 resolve만 호출했을 때
const promise1 = new Promise((resolve, reject) => {
resolve();
});
// promise를 호출하고, 비동기 작업이 끝났을 때 성공, 실패 여부에 따른 뒷처리
promise1
.then(() => {
console.log("then!");
})
.catch(() => {
console.log("catch");
});
// 실행결과 => then!
// 2. promise1 내에서 reject만 호출했을 때
const promise1 = new Promise((resolve, reject) => {
reject();
});
promise1
.then(() => {
console.log("then!");
})
.catch(() => {
console.log("catch!");
});
// 실행결과 => catch!
```
- then의 인자로 then을 출력해주는 콜백함수를 전달해주었고, 연속적으로 catch에도 함수를 전달해주었다.
- 예제 1) promise1에서 resolve()가 바로 호출됐기 때문에 성공으로 간주 ⇒ `then` 에 있는 동작만 실행됨.
- 예제 2) promise1에서 reject()가 바로 호출됐기 때문에 성공으로 간주 ⇒ `catch`에 있는 동작만 실행됨.
Promise 재사용하기
:new Promise() 를 하는 순간 비동기 작업이 시작된다. 그러면 비슷한 비동기 작업을 수행할 때마다 매번 new Promise() 를 해줘야 할까?
function startAsync(age) {
return new Promise((resolve, reject) => {
if (age > 20) resolve();
else reject();
});
}
const promise1 = startAsync(25);
promise1
.then(() => {
console.log("1 then");
})
.catch(() => {
console.log("1 catch");
});
const promise2 = startAsync(15);
promise2
.then(() => {
console.log("2 then");
})
.catch(() => {
console.log("2 catch");
});
// 실행 결과
1 then
2 catch
startAsync함수를 호출하는 순간,new Promise()가 실행되어서 바로 비동기 작업이 시작된다.- 비동기 작업이 성공할지 실패할지는 장담할 수 없음 ⇒
then,catch로 후속 동작을 지정함. promise1은 성공함 ⇒then으로 지정한 동작만 수행함.promise2은 실패함 ⇒catch로 지정한 동작만 수행함.
⇒ 그럴 필요없이 함수 내에서return new Promise()해주면 된다.
promise에서 실행한 작업 결과를 전달하기
resolve,reject함수에 인자를 전달하면then,catch에서 사용할 수 있음
function startAsync(age) {
return new Promise((resolve, reject) => {
if (age > 20) resolve(`${age} success`);
else reject(new Error(`${age} is not over 20`));
});
}
const promise1 = startAsync(25);
promise1
.then((value) => {
// value => resolve 함수에 전달된 `${age} success`
console.log(value);
})
.catch((err) => {
console.log(err.message);
});
const promise2 = startAsync(15);
promise2
.then((value) => {
console.log(value);
})
.catch((err) => {
console.log(err.message);
});
// 실행 결과
25 success
15 is not over 20
executor 함수 작성할 때 고려사항
- executor 내부에서 에러가 throw 된다면 해당 에러로
reject가 수행된다.
// reject됨 => catch 작동함.
const throwError = new Promise((resolve, reject) => {
throw Error("error");
});
throwError
.then(() => console.log("throwError success"))
.catch(() => console.log("throwError catched"));
// 실행 결과
throwError catched
- executor의 리턴값은 무시된다.
const ret = new Promise((resovle, reject) => {
return "returned";
});
ret
.then(() => {
console.log("ret success");
})
.catch(() => {
console.log("ret catched");
});
// 실행 결과 없음 => return값이 아무런 영향을 끼치지 않음.
- 첫번째
reject혹은resolve만 유효하다. (두번째부터는 무시됨. 이미 해당 함수가 호출됐다면 throw 또한 무시된다.)
⇒ 어차피 첫번째 resolve, reject만 영향을 주기 때문에 해당 함수가 호출되면 바로 return해서 비동기 작업을 빠져나간다.
// 1. resolve만 실행되는 경우
const several1 = new Promise((resolve, reject) => {
resolve();
reject();
});
several1
.then(() => console.log("several1 success"))
.catch(() => console.log("several1 catched"));
// 실행 결과 : several success
// 2. reject만 실행되는 경우
const several2 = new Promise((resolve, reject) => {
reject();
resolve();
});
several2
.then(() => console.log("several2 success"))
.catch(() => console.log("several2 catched"));
// 실행결과 : several2 catched
// 3. resolve만 실행되는 경우
const several3 = new Promise((resolve, reject) => {
resolve();
throw new Error("error");
});
several3
.then(() => console.log("several3 success"))
.catch(() => console.log("several3 catched"));
// 실행결과 : several3 success
setTimeout을 Promise 버전으로 만들기
setTimeout(callback, delay);
- setTimeout : 인자로 주어진 콜백함수를 주어진 delay초 뒤에 실행한다.⇒ 실패했을 때는 일단 없다고 가정함.
⇒ 주어진 delay를 기다리는 것을 완료한다면 성공(fulfilled), 성공했다면 callback을 실행함.
function setTimeoutPromise(delay) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, delay);
});
}
console.log("promise가 시작됩니다.");
const promise = setTimeoutPromise(1000);
promise.then(() => {
console.log("끝났습니다!");
});
// Refactoring
function setTimeoutPromise(delay) {
return new Promise(
(resolve) => setTimeout(resolve, delay) // reject 생략 및 화살표 함수 축약 가능
);
}
console.log("promise가 시작됩니다.");
const promise = setTimeoutPromise(1000);
promise.then(() => {
console.log("끝났습니다!");
});
setTimeoutPromise 함수를 호출했을 때 벌어지는 일
setTimeout이 즉시 실행됨.delay밀리 초 뒤에resolve()가 실행됨.- 해당 promise의
then으로 연결된 후속 작업이 실행된다. - 🍓 기다렸다는 사실만 중요하므로 resolve에 별다른 값을 전달하지 않았음.