setTimeout
만약 아래 코드를 실행하면 결과가 어떻게 될까?
A-B-C 순으로 출력이 될까 아니면 A-C-B순으로 출력이 될까?
console.log("A");
setTimeout(function () {
console.log("B");
}, 0);
console.log("C");
만약 자바스크립트가 멀티쓰레드라면 실행할 때마다 A-B-C로 출력되거나 A-C-B로 다르게 출력이 될 것이다.
메인쓰레드 외에 setTimeout이라는 부분을 별도의 쓰레드가 담당하게 되어서 두개의 쓰레드가 경합하면 메인쓰레드가 C를 출력할지 B를 출력할지 CPU가 다르게 할당할 것이기 때문이다.
그러나, 자바스크립트는 싱글쓰레드 언어이다. 따라서 setTimeout 부분을 별도의 쓰레드로 할당하지 않고 나중에 실행하기 위해서 큐에 넣어둔다.
만약 메인 쓰레드가 C까지 출력하고 나면 그 다음에 큐에 쌓여진 job을 처리한다.
따라서 항상 A-C-B 순으로 출력된다.
만약 아래처럼 무한루프가 존재하면 큐에 쌓여진 job을 처리할 수 없으므로 출력은 A-C까지만 되고 더이상 출력되지 않는다.
console.log("A");
setTimeout(function () {
console.log("B");
}, 0);
console.log("C");
while(true) {}
이벤트 큐에 들어간 실행시간이 있는 콜백 함수들은 실행시간이 짧은 순서대로 배치된다!
- 아래 코드 실행 결과는 3, 1, 2 순으로 출력된다!
- 시간이 짧은 순으로 앞에 배치되기 때문!
setTimeout(()=>{
console.log(1)
}, 900)
setTimeout(()=>{
console.log(2)
}, 1000)
setTimeout(()=>{
console.log(3)
}, 500)
콜백 패턴
아래는 A-B-C 순으로 1초 간격으로 순차적으로 출력하는 코드이다.
setTimeout(function() {
console.log("A");
setTimeout(function() {
console.log("B");
setTimeout(function() {
console.log("C");
}, 1000);
}, 1000);
}, 1000);
setTimeout의 첫번째 파라메터인 펑션은 1초후에 실행될 펑션이다. 해당 펑션은 큐에 쌓이고 1초 후에 메인쓰레드가 실행한다. 이렇게 비동기로 실행되는 펑션을 콜백 펑션이라고 하고 이러한 패턴을 콜백 패턴이라고한다.
하지만, 콜백 패턴을 중첩해서 사용하면 문장이 깊어지고 코드의 가독성이 떨어진다. 이런 것을 callback hell, callback triangle이라고 하며 이것을 해결하기 위해 Promise 패턴이 등장했다.
Promise
콜백 중첩으로 인한 코드 가독성을 해결하기 위해서 Promise 패턴이 제안되었다. 이 패턴은 코드가 병렬로 배열되어서 가독성이 높아지고 또한 오류 처리등에 대해서도 추가되었다. es5에서 Promise가 많이 사용되자 ES6에서부터는 Promise가 표준 객체가 되었다.
Promise 객체는 아래와 같은 문법으로 만들 수 있다.
new Promise() 메서드를 호출할 때 콜백 함수를 선언할 수 있고 파라미터는 resolve와 reject이다.
resolve나 reject를 호출해서 상태를 변경할 수 있다.
var promise = new Promise(function(resolve, reject) {
});
Promise를 사용하는데 있어서 아래와 같은 것들은 기초적으로 기억해야 한다.
- Promise 패턴은 비동기 작업을 순차적으로 처리한다. (중요!!)
- Promise는 new 키워드로 선언과 동시에 실행된다.
- Promise는 .then() 콜백 메서드에서 비동기 작업의 결과를 처리한다.
- Promise는 반드시 resolve 되거나 reject 되어야 한다.
만일 어느한쪽도 리턴되지 않으면 .then() 이 호출되지 않는다.
비유하자면 약속을 했으면 반드시 지켜지거나 파기되어야 한다. 어느쪽도 아니면 아직 pending 상태가 되어서 .then()이 호출 되지 않는다.
Promise에는 아래와 같은 상태가 있다.
- Pending(대기) : 비동기 처리 로직이 아직 완료되지 않은 상태. fullfilled 또는 reject 되기 전 상태.
- Fulfilled(완료) : 비동기 처리가 완료되어 Promise가 결과 값을 반환해준 상태
- Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태
Pending
var pending = new Promise((resolve, reject) => {});
console.log(pending);
Promise { <pending> }
Fulfilled
var fulfilled = new Promise((resolve, reject) => resolve('fulfilled'))
console.log(fulfilled);
resolve로 넘겨준 fulfilled값인 상태로 변한 것을 알 수 있다.
Promise { 'fulfilled' }
rejected
var rejected = new Promise((resolve, reject) => {
reject('rejected');
});
console.log(rejected);
Promise { <rejected> 'rejected' }
then()
then() 메서드는 Promise를 리턴하고 두 개의 콜백 함수를 인수로 받는다.
하나는 Promise가 이행했을 때(resolve를 호출했을 때), 다른 하나는 거부했을 때(reject를 호출했을 때)를 위한 콜백 함수이다.
Promise 객체에서 resolve가 호출되면 해당 Promise 객체뒤에 then()을 붙여서 then() 안의 콜백 메서드에서 비동기 작업의 결과를 처리할 수 있다!
resolve가 호출되면 두번째 파라미터는 생략할 수 있다.
var fulfilled = new Promise((resolve, reject) => resolve('fulfilled'))
fulfilled.then(data => console.log(data), error => console.log(error));
//fulfilled
Promise 객체에서 reject가 호출되면 then()의 두번째 파라미터의 콜백함수가 불린다.
var rejected = new Promise((resolve, reject) => {
reject('rejected');
});
rejected.then(data => console.log(data), error => console.log(error));
//rejected
두번째 파라미터 대신 .catch()를 사용해서 처리할 수도 있다.
var rejected = new Promise((resolve, reject) => {
reject('rejected');
});
rejected.then(data => console.log(data)).catch(error => console.log(error));
//rejected
만약 Promise 객체가 에러를 리턴하면 reject로 리턴했을 때와 마찬가지로 .then()의 두번째 파라미터나 .catch()로 에러를 캐치할 수 있다.
var error = new Promise((resolve, reject) => {
throw 'error';
});
error.then(data => console.log(data)).catch(error => console.log(error));
//error
체이닝
비동기 함수를 순차적으로 처리할 때 유용하다.
Promise 체이닝이 가능한 이유는 .then()을 호출하면 Promise가 반환되기 때문이다. 반환된 프라미스엔 당연히 .then()을 호출할 수 있다
만약 아래 코드처럼 1초마다 순차적으로 A, B, C를 출력하는 코드를 Promise 패턴으로 바꿔야하면 어떻게 할까?
setTimeout(() => {
console.log('A');
setTimeout(() => {
console.log('B');
setTimeout(() => {
console.log('c');
}, 1000);
}, 1000);
}, 1000);
Promise에서는 아래와 같이 .then()에서 return하면 다시 .then()에서 받을 수 있다.
new Promise(resolve => {
resolve('A');
}).then(data => {
console.log(data);
return 'B';
}).then(data => {
console.log(data);
return 'C';
}).then(data => {
console.log(data);
})
위의 코드를 그림으로 표현하면 아래와 같다.
.then() 안의 콜백 함수가 Promise를 생성하거나 반환하는 경우도 있다.
이 경우 이어지는 then()안의 콜백 함수는 Promise가 처리될 때까지 기다리다가 처리가 완료되면 그 결과를 받는다.
new Promise(resolve => {
setTimeout(() => {
resolve('A');
}, 1000);
}).then(data => {
console.log(data);
return new Promise(resolve => {
setTimeout(() => {
resolve('B');
}, 1000)
})
}).then(data => {
console.log(data);
return new Promise(resolve => {
setTimeout(() => {
resolve('C');
}, 1000)
})
}).then(data => console.log(data));
예시에서 첫 번째 .then은 A을 출력하고 new Promise(…)를 반환한다.
1초 후 이 Promise가 이행되고 그 결과(resolve의 인수인 B)는 두 번째 .then으로 전달된다.
두 번째 핸들러는 B를 출력하고 동일한 과정이 반복된다.
위의 결과는 아래와 같다. 다만 1초마다 resolve하느냐, 출력하느냐의 차이다.
new Promise(resolve => {
resolve('A');
}).then(data => {
setTimeout(() => console.log(data),1000);
return 'B';
}).then(data => {
setTimeout(() => console.log(data),1000);
return 'C';
}).then(data => {
setTimeout(() => console.log(data),1000);
})
주의사항
체이닝
setTimeout()에서 return 써서 반환해서 체이닝 하려고 하지마셈!! 이상한 값 나와요!!
아래처럼 하지 말라는 소리!!
new Promise(resolve => {
resolve('A');
}).then(data => {
setTimeout(() => {
console.log(data);
return('B');
},1000);
}).then(data => {
setTimeout(() => {
console.log(data);
return('C');
},1000);
}).then(data => {
setTimeout(() => console.log(data),1000);
})
/// A undefined undefined
Promise 객체
Promise객체 안에서 return해서 밖에 변수에 값을 저장하지 마셈!! promise는 promise를 리턴하니까!!
var a1;
const b1 = () => {
return new Promise(() => {
return 'B';
setTimeout(() => {
}, 1000);
})
};
a1 = b1();
console.log(a1);
//Promise { <pending> }
then()
아래처럼 then에서 resolve된 값을 가져다 쓰려고도 하면 안댐!! then을 호출하면 promise를 리턴하기 때문이다!
또한 then 안의 함수는 콜백함수이기 때문에 동기가 맞지 않는다!!
a = new Promise(resolve => {
resolve('HI');
});
b = a.then(data => {
return(data);
});
console.log(b);
//Promise { <pending> }
소소하지만 중요한 팁
아래처럼 변수를 선언하고 then 안에서 함수에서 대입을 해주면 값은 들어가지만 출력을 해보면 undefined로 나온다. then 안의 함수가 콜백함수이기 때문이다!
a = new Promise(resolve => {
resolve('HI');
});
var b;
a.then(data => {
b = data;
});
console.log(b);
//undefined
그래서 아래처럼 출력하는 부분도 콜백함수로 만들어주면 값이 제대로 나옴!
a = new Promise(resolve => {
resolve('HI');
});
var b;
a.then(data => {
b = data;
});
setTimeout(()=>{
console.log(b);
},1000);
//HI
Promise.all()
만약 비동기 작업을 체이닝처럼 순차적으로 처리하는게 아니라 한꺼번에 동시로 병렬로 실행하고 싶으면 all()을 사용하면 된다.
all()을 사용해서 비동기 작업을 동시에 처리하고 작업이 모두 끝나고 나서야 then()이 호출된다. 예를 들어 세개의 비동기 작업 시간이 각각 1초, 2초, 3초라면 3초 후에 then()이 호출된다!
const a = new Promise(resolve => {
setTimeout(() => {
resolve('A');
}, 1000);
});
const b = new Promise(resolve => {
setTimeout(() => {
resolve('B');
}, 2000);
});
const c = new Promise(resolve => {
setTimeout(() => {
resolve('C');
}, 3000);
});
Promise.all([a, b, c]).then(data => console.log(data));
[ 'A', 'B', 'C' ]
'Javascript > ES5 & ES6' 카테고리의 다른 글
Scope, =, 삼항연산자 (0) | 2021.07.07 |
---|---|
async/await (0) | 2021.06.04 |
객체 생성, Class (0) | 2021.06.03 |
lodash (0) | 2021.06.02 |
타입, Array, Array에 유용한 메서드 (0) | 2021.06.02 |
주니어 개발자에욤
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!