비동기로 작동하는 코드를 제어하는 또다른 방법, 그리고 Callback Hell
을 방지하는 역할을 하는
Promise
에 대해 알아보자.
promise
는 class이기 때문에 new
키워드를 통해 Promise
객체를 생성한다.
이 promise
는 비동기 처리를 수행할 콜백 함수(executor
)를 인수로 전달받는데
이 콜백 함수는 resolve
, reject
함수를 인수로 전달받는다.
promise
객체가 생성되면 executor
는 자동으로 실행되고 작성했던 코드들이 작동된다.
정상적으로 처리 되었다면 resolve
함수를 호출하고
에러가 발생했을 경우에는 reject
함수를 호출하면된다.
정상적으로 처리된 경우의 프로미스 객체
에러가 발생한 경우의 프로미스 객체
promise 객체는 state
와 result
내부 프로퍼티를 가진다.
state
기본 상태는 `pending`(대기)이다. 비동기처리를 수행할 콜백 함수(`executor`)가 성공적으로 작동했다면 `fulfilled`로 변경되고, 에러가 발생하면 `rejected` 된다.
result
기본 상태는 `undefined`이다. 비동기 처리를 수행할 콜백 함수(`executor`)가 성공적으로 작동해 `resolve(value)`가 호출되면 `value`로, 에러가 발생하여 `reject(error)`가 호출되면 `error`로 변한다.
이 두가지 내부 프로퍼티직접 접근은 불가능하며,
.then
, .catch
, .finally
의 메서드를 사용해야 접근할 수 있다.
콜백 함수(executor
)에 작성했던 코드들이 정삭처리 되었다면 resolve
함수를 호출하고 .then
메서드로 접근 가능하다.
또한 .then
안에서 리턴한 값이 promise
일 경우
내부 프로퍼티인 result
를 다음 .then
의 콜백 함수의 인자로 받아오고
.then
안에서 리턴한 값이 promise
가 아닐 경우
리턴한 값을 .then
의 콜백 함수의 인자로 받아올 수 있다.
let promise = new Promise((resolve, reject) => {
resolve("성공");
});
promise.then(value => {
console.log(value);
// "성공"
})
콜백 함수(executor
)에 작성했던 코드들이 에러가 발생했을 경우 reject
함수를 호출하고
carch
메서드로 접근 가능하다.
let promise = new Promise(function(resolve, reject) {
reject(new Error("에러"))
});
promise.catch(error => {
console.log(error);
// Error: 에러
})
콜백 함수(executor
)에 작성했던 코드들의 정상 처리 여부와 상관없이 .finally
메서드로 접근 가능하다.
let promise = new Promise(function(resolve, reject) {
resolve("성공");
});
promise
.then(value => {
console.log(value);
// "성공"
})
.catch(error => {
console.log(error);
})
.finally(() => {
console.log("성공이든 실패든 작동!");
// "성공이든 실패든 작동!"
})
Promise chaining이 필요한 경우는 비동기 작업을 순차적으로 진행해야 하는 경우다.
Promise chaining이 가능한 이유는 위에 살펴본 세가지 메서드들이 promise
를 리턴하기 때문이다.
따라서 .then
을 통해 연결할 수 있고, 에러가 발생할 경우에는 .catch
로 처리하면 된다.
let promise = new Promise(function(resolve, reject) {
resolve('성공');
...
});
promise
.then((value) => {
console.log(value);
return '성공';
})
.then((value) => {
console.log(value);
return '성공';
})
.then((value) => {
console.log(value);
return '성공';
})
.catch((error) => {
console.log(error);
return '실패';
})
.finally(() => {
console.log('성공이든 실패든 작동!');
});
promise.all()
은 여러 개의 비동기 작업을 동시에 처리하고 싶을 때 사용한다.
인자로는 배열을 받고 해당 배열에 있는 모든 promise
에서 executor
안에 작성했던 코드들이
정상적으로 처리되었다면 결과를 배열에 저장해 새로운 promise
를 반환해준다.
promise chaining
을 사용할 경우 코드들이 순차적으로 동작되고 같은 코드가 중복되는 현상이있다.
// promise chaining
const result = [];
promiseOne()
.then(value => {
result.push(value);
return promiseTwo();
})
.then(value => {
result.push(value);
return promiseThree();
})
.then(value => {
result.push(value);
console.log(result);
// ['1초', '2초', '3초']
})
이러한 문제들을 promise.all()
을 사용해 해결할 수 있다.
이는 비동기 작업들을 동시에 처리해 promise chaining
보다 간결하다.
// promise.all()
Promise.all([promiseOne(), promiseTwo(), promiseThree()])
.then((value) => console.log(value))
// ['1초', '2초', '3초']
.catch((err) => console.log(err));
💡 `promise.all()`은 인자로 받는 배열에 있는 promise 중
하나라도 에러가 발생되면 promise의 state와 상관없이 즉시 종료된다.
Callback Hell
처럼 promise Hell
도 존재한다.
promise
를 통해 비동기 코드의 순서를 제어할 수 있지만,
코드가 길어질 수록 복잡해지고 가독성이 낮아진다.
ex> promise Hell
async / await
를 이용해 복잡한 promise
코드를 간결하게 작성할 수 있게 되었다.
사용법은 함수 앞에 async
키워드를 사용하고 async
함수 내에서만 await
키워드를 사용하면 된다.
이렇게 작성된 코드는 await
키워드가 작성된 코드가 동작한 후에 다음 순서의 코드가 동작한다.
// 함수 선언식
async function funcDeclarations() {
await 작성하고자 하는 코드
...
}
// 함수 표현식
const funcExpression = async function () {
await 작성하고자 하는 코드
...
}
// 화살표 함수
const ArrowFunc = async () => {
await 작성하고자 하는 코드
...
}