1. 동기와 비동기
js는 동기적인 언어입니다. hoisting(var, function 선언이 최상단으로 올라가는 것)이 된 이후 부터 코드가 나타나는 순서대로 실행이 됩니다. callback 함수에 대해 다시 알아봅니다.
callback함수는 우리가 전달해준 함수를 원할때 다시 실행시켜 달라는 의미입니다. browserAPI인 setTimeout의 정의를 살펴보면 handeler라는 callback함수를 파라미터로 받는 것을 볼 수 있습니다.
console.log('1');
setTimeout(function () {
console.log('2');
}, 1000);
console.log('3');
절차대로 1을 찍고 2에서는 browserAPI를 만났으므로 browser에서 1초의 시간이 지난 후 2를 출력하라는 신호를 보내게 되어 콘솔이 아래와 같이 찍히게 됩니다. 이것이 async 입니다.
1.1. 콜백 정리
콜백은 비동기적 상황에서만 사용하는 것은 아닙니다. 비동기와 동기 콜백으로 나뉘어 집니다. 위의 자바스크립트와 합쳐서 생각해봅니다.
// 동기적인 콜백
function printImmeditaely(print) { // 함수는 hositng됩니다.
print();
}
printImmediately(()=>console.log('hello'));
이제 비동기적 콜백을 알아봅니다.
function printWithDelay(print, timeout){
setTimeout(print, timeout);
}
printWithDelay(()=>console.log('async callback'), 2000);
1.2. 콜백 지옥(콜백 )
콜백은 유용하지만 너무 남발하게되면 콜백지옥에 빠지기 쉽습니다. 콜백지옥을 체험하는 코드를 작성해봅니다.
// 콜백지옥 로그인 성공시 성공했다는 onSuccess 콜백함수, 에러시 onError 콜백 함수, 로그인 성공시 역할을 불러오는 함수실행
class UserStorage {
loginUser(id, password, onSuccess, onError) {
setTimeout(()=>{
if(로그인 로직 생략){
onSuccess(id)
}else{
onError(new Error('not found'));
}
}, 2000);
}
getRoles(user, onSuccess, onError) {
setTimeout(()=>{
if(user === 'lala') {
onSuccess({ name: 'lala', role: 'admin'});
}else{
onError(new Error('no access'));
}
},1000);
}
}
const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = prompt('enter your password');
userStorage.loginUser(
id,
password,
user => {
userStorage.getRoles(
user,
userWithRole => {
alert(`Hello ${userWithRole.name}, you have a ${userWithRole.role} role`);
},
error => {
console.log(error);
}
);
},
error => {
console.log(error);
}
);
이처럼 콜백안에 콜백을 계속 전달하는 것을 콜백 체인이라하며 이는 가독성이 매우 떨어집니다.
2. promise
promise는 callback을 사용하지 않고 비동기코드를 처리할 수 있는 API입니다. promise는 js안에 내장된 object입니다.
2.1. state
promise의 상태에는 pending(진행중), fulfilled(성공), rejected(실패) 상태가 있습니다. resolve 처리를 해주지 않으면 계속 pending 상태가 됩니다.
2.2. producer
const promise = new Promise(resolve, reject) => { // 결과가 성공일 때 호출하는 resolve, 오류일때 실행되는 reject
// doing executor something(network, read files...)
resolve('lala');
reject(new Error('no network'));
})
promise object 생성시 executor함수가 바로 실행됩니다. executor 안에는 상태에 따라 호출되는 resolve와 reject함수가 들어갑니다. 만약 버튼을 눌렀을 때 비동기동작을 해야한다면, promise가 생성되는 동시에 executor가 실행되는 점에 주의해야 합니다.
2.3. consumers
then, catch, finally를 통해 값을 받아 올 수 있습니다.
promise
.then((vlaue) => { // 올바르게 promise를 수행하면 value로 resolve 콜백함수에 전달한 값이 파라미터로 들어옵니다.
console.log(value);
})
.catch(error => { // reject의 error 를 전달 받아 처리합니다.
console.log(error);
})
.finally(()=>{console.log('finaylly')}); // 무조건 한번 실행됩니다.
2.4. promise chaining
const fetchNumber = new Promise((resolve, rejected) => {
setTimeout(() => resolve(1), 1000);
});
fetchNumber
.then(num => num *2)
.then(num => num *3)
.then(num => {
return new Promise((resolve, reject) => { // 새로운 promise 전달
setTimeout(() => resolve(num -1), 1000);
});
})
.then(num => console.log(num));
2.5. call back 지옥 함수 리팩토링
1.2. 에서 보았던 콜백 지옥 함수를 promise chainnig으로 변경해 봅니다.
class UserStorage {
loginUser(id, password) {
return new Promise((resolve, reject) => {
setTimeout(()=>{
if(로그인 로직 생략){
resolve(id);
}else{
reject(new Error('not found'));
}
}, 2000);
});
}
getRoles(user) {
return new Promise((resolve, reject) => {
setTimeout(()=>{
if(user === 'lala') {
resolve({ name: 'lala', role: 'admin'});
}else{
reject(new Error('no access'));
}
},1000);
});
}
}
const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = prompt('enter your password');
userStorage.loginUser(id, password)
.then(user => userStorage.getRoles) // 인자가 같다면 생략가능
.then(alert(`Hello ${user.name}, you have a ${user.role} role!`)) // user => 를 생략
.catch(console.log);
3. async와 await
async와 await는 promise를 좀더 간편하고 동기적으로 실행되는 것 처럼 보이게 만들어줍니다. 계속 then으로 chaining을 하게되면 코드가 난잡해질 수도 있습니다. 이때 좀더 간편한 async, await API를 사용하면 간결하게 사용할 수 있도록 도와줍니다. async와 await은 promise위에 존재하는 API입니다. 이런 API를 syntactic sugar라고 합니다.
3.1. async
function fetchUser() {
// do network request in 10 secs.. 오래 걸리는 코드라고 가정
return 'lala';
}
const user = fetchUser();
console.log(user);
위와 같은 코드는 동기적으로 수행되어 10초동안 멈춰있다가 남은 데이터를 모두 보여주게 됩니다. 이럴때는 비동기 처리를 하여 보여줄 수 있는 페이지는 먼저 보여준 후, 오래걸리는 작업을 비동기 처리를 해주는게 좋습니다.
function fetchUser() {
return new Promise((resolve, rejected) => {
// do network request in 10secs...
resolve 'lala'; // resolve를 사용하지않으면 계속 pending 상태
});
}
const user = fetchUser();
user.then(console.log);
console.log(user);
위의 비동기적 코드를 async API를 이용하여 더 간결하게 바꿀 수 있습니다. aync는 promise를 만들어주는 키워드라고 볼 수 있습니다.
async function fetchUser() {
// do network request in 10 secs...
return 'lala';
}
const user = fetchUser();
user.then(console.log);
console.log(user);
3.2. await
await는 async가 붙은 함수에서만 사용할 수 있습니다. await 키워드를 사용하게 되면 then을 사용하지 않고 일정시간 기다린 후 값을 return시킬 수 있습니다.
promise도 chaining을 반복적으로 사용하면 콜백지옥에 빠질 수 있습니다.
이러한 코드도 async API를 사용하면 간단하게 작성 할 수 있습니다.
3.3. await 병렬처리
비동기들을 각각 병렬처리를 하도록 개선해봅니다. async 키워드를 붙이면 promise를 만드므로 execute가 바로 실행되는 점을 참고합니다.
위의 코드는 예시를 위한 코드일 뿐, 병렬처리를 위한 promise API를 지원합니다.(all, race)
댓글남기기