비동기 함수를 호출하면 함수 내부의 비동기로 동작하는 코드가 완료되지 않았더라도 기다리지 않고 즉시 종료된다.
따라서 비동기로 동작하는 코드에서 처리 결과를 외부로 반환하거나 상위 스코프의 변수에 할당할 수 없다.
let a = 0;
setTimeout(() => {
a = 100;
}, 0);
console.log(a); // 0
위 예제에서 setTimeout
비동기 함수 내부에서 0 으로 할당되었던 a 변수를 100 으로 변경하였다.
하지만 setTimeout
비동기 함수는 a
변수를 100으로 바꾸는 동작을 기다리지 않고 즉시 종료되고, 다음 코드를 실행하여 console.log(a)
가 실행되어 변경되지 않은 값인 0이 출력된다.
setTimeout
호출setTimeout
함수를 실행하면 Web API
에 setTimeout
을 요청하고, 콜백 함수를 전달한다.setTimeout
함수가 종료되면 실행 컨텍스트가 콜 스택에서 pop (Web API 요청 이후 응답을 기다리지 않음!)console.log(a)
호출되어 0 출력Web API
는 요청 받은 setTimeout
을 완료하고, 전달받은 콜백 함수를 태스크큐에 전달된다.
태스크큐에 저장되어 대기하다가 콜 스택이 비면 이벤트 루프에 의해 콜 스택으로 푸쉬되어 실행된다.
따라서 이미 console.log(a)
가 종료 되고 콜 스택이 빈 상태여야 비동기 함수의 콜백 함수가 실행되어 a 변수를 바꾸게 된다.
비동기 함수는 비동기 처리 결과를 외부로 전달하거나 상위 스코프의 변수에 할당할 수 없기 때문에, 비동기 함수의 처리 결과로 어떤 작업을 다시 수행하고자 할 때는 비동기 함수 내부에서 수행하여야한다.
비동기 처리 결과에 대해 후속 처리를 하기 위해 콜백함수를 전달할 수 있다.
콜백 함수를 통해 비동기 처리 결과에 대한 후속 처리를 수행하는 비동기 함수가 비동기 처리 결과를 가지고 또다시 비동기 함수를 호출할 때 콜백 함수 호출이 중첩되어 복잡도가 높아지는 현상
step1(function (value1) {
step2(function (value2) {
step3(function (value3) {
step4(function (value4) {
step5(function (value5) {
step6(function (value6) {
// Do something with value6
});
});
});
});
});
});
try {
setTimeout(()=> {throw New Error('Error');}, 1000);
} catch(e) {
console.error(e); //캐치되지 않는다!!
}
setTimeout
호출 → setTimeout
함수 실행 컨텍스트 생성 → Call Stack push → 실행 -> 콜백함수의 종료를 기다리지 않고 종료, 콜스택에서 제거콜백 헬과 에러 처리가 어렵다는 문제로 인해 ES6 에서 Promise 가 도입되었다.
// Promise 는 `Promise` 생성자 함수를 통해 인스턴스화 한다.
const promise = new Promise(function (resolve, reject) { // Promise 생성자 함수는 인수로 콜백 함수(`executor`) 를 전달받는다.
// executor 는 `resolve` 와 `reject` 함수를 인수로 전달받으며 Promise 가 만들어 질 때 자동으로 실행된다.
console.log('executer 수행');
// 비동기 처리
const value = 1;
// 비동기 처리가 성공하면 비동기 처리 결과(value)를 resolve 함수에 인수로 전달하면서 호출
if (value > 1) {
resolve(value);
} else { // 실패하면 에러(reason)를 reject 함수에 인수로 전달하면서 호출
reject(new Error('fail'));
}
});
Promise 는 비동기 처리 상태 정보를 갖는다.
상태 정보 | 의미 | 상태 변경 조건 |
---|---|---|
pending | 아직 수행 안됨 | 프로미즈 생성된 직후 상태. 아직 resolve 또는 reject 호출 안됨 |
fulfilled | 비동기 처리 성공 | resolve 함수 호출 |
rejected | 비동기 처리 실패 | reject 함수 호출 |
settled | 비동기 처리 수행 완료 | resolve 또는 reject 호출 됨 |
fulfilled 된 promise
fulfilled 된 Promise 를 만들기 위해 resolve 함수를 호출하는 Promise 객체를 만들어 보자.
PromiseState 에 fulfilled
, PromiseResult 에 success
가 담겨 있는 것을 볼 수 있다.
rejected 된 promise
rejected 된 Promise 를 만들기 위해 Error 를 인수로 받는 reject 함수를 호출하는 Promise 객체를 만들어 보자.
PromiseState 에 rejected
, PromiseResult 에 Error
객체가 담겨 있는 것을 볼 수 있다.
즉, Promise 는 비동기 처리 상태와 처리 결과를 관리하는 객체다.
비동기 처리가 성공하면 성공한 값을 가지고 어떤 작업을 수행하거나, 비동기 처리가 실패해서 에러 처리를 하는 등의 처리를 위해서 Promise 는 후속 메서드 then
, catch
, finally
를 제공한다.
Promise 의 비동기 처리 상태가 변화하면 이러한 후속 메서드에 인수로 전달한 콜백 함수가 호출된다.
then
fulfilled
상태가 되면 호출될 콜백 함수와, rejected
상태가 되면 호출될 콜백 함수를 받을 수 있다.
new Promise((resolve, reject) => resolve(1)).then(
value => {
console.log(value);
},
error => {
console.log(error);
}
);
// 1 출력
new Promise((resolve, reject) => reject(new Error('error'))).then(
value => {
console.log(value);
},
error => {
console.log(error);
}
);
// Error: error 출력
catch
rejected
상태가 되면 호출될 콜백 함수만 받을 수 있다.
new Promise((resolve, reject) => reject(new Error('error')))
.then(value => {
console.log(value);
})
.catch(error => {
console.error(error);
});
// Error: error 출력
finally
settled
상태가 될 때 호출될 콜백 함수를 받을 수 있다.
후속 메서드 then
, catch
, finally
에 전달되는 콜백 함수는 항상 Promise 를 반환한다.
만약 콜백 함수가 프로미스가 아닌 값을 반환하면 암묵적으로 Promise 로 생성해서 반환한다.
콜백 함수가 반환한 값은 프로미스 체이닝
을 통해 다음 .then 핸들러에 전달되고, Promise 로 생성되어 반환되기 때문에 후속 처리 메서드를 호출할 수 있다.
new Promise((resolve, reject) => resolve(1))
.then(value => {
console.log(value);
return value + 1;
})
.then(value => {
console.log(value);
return value + 1;
})
.then(value => {
console.log(value);
return value + 1;
})
.catch(error => {
console.error(error);
});
// 1
// 2
// 3
만약 콜백 함수가 아무것도 반환하지 않는다면 다음 then 핸들러에서 전달 받을 수 없다.
new Promise((resolve, reject) => resolve(1))
.then(value => {
console.log(value);
})
.then(value => {
console.log(value);
return value + 1;
})
.then(value => {
console.log(value);
return value + 1;
})
.catch(error => {
console.error(error);
});
// 1
// undefined
// NaN
첫번째 then handler 에서 아무것도 반환하지 않았기 때문에 두번째 then handler 에서 undefined 가 출력되었다.
두번째 then handler 에서 undefined + 1 한 값을 반환했기 때문에 세번째 then handler 에서 NaN 이 출력된다.
then
의 두번째 콜백 함수와 catch
를 활용해 에러 처리를 할 수 있다.
두 가지 방식의 가장 큰 차이점은, then
의 두번째 콜백 함수는 첫번째 콜백 함수에서 발생한 에러를 캐치하지 못한다는 것이다.
new Promise((resolve, reject) => resolve('1')).then(
value => {
console.log(value);
throw new Error('error');
},
error => {
console.log(error);
}
);
// 1
new Promise((resolve, reject) => resolve('1'))
.then(value => {
console.log(value);
throw new Error('error');
})
.catch(error => {
console.error(error);
});
// 1
// Error: error
catch
메서드를 모든 then
메서드를 호출한 이후에 호출하면 then 메서드 내부에서 발생한 에러까지 모두 캐치할 수 있기 때문에 에러 처리는 catch 메서드에서 하는 것을 권장한다고 한다.
new Promise((resolve, reject) => resolve(1))
.then(value => {
console.log(value);
return value + 1;
})
.then(value => {
console.log(value);
throw new Error('error');
})
.catch(error => {
console.error(error);
});
// 1
// 2
// Error: error
new Promise((resolve, reject) => resolve(1))
.then(value => {
console.log(value);
throw new Error('error');
})
.then(value => { // 첫번째 then 에서 error 가 발생했기 때문에 resolve 되지 않아 두번째 then 이 호출되지 않는다.
console.log(value);
return value + 1;
})
.catch(error => {
console.error(error);
});
// 1
// Error: error