JavaScript
20210217_JavaScript08, async, Callback function, Promise Object
JavaScript08
- JavaScript에 대해 공부하기 위해서 유튜브 '드림코딩by엘리' JavaScript 강의를 듣고 글을 쓰고 있다.(강의가 엄청 잘되 있으니 강의를 찾아보자!)
JavaScript Callback 비동기 처리의 시작 콜백 이해하기
동기(sync)와 비동기(async)
- Synchronous(동기) : 코드 블럭이 쓰여진 순으로 즉시실행하는 것
- javaScript는 동기적인데, 호이스팅 이후에 코드블럭을 순서대로 실행한다.
(JavaScript is synchronous., Execute the code block in oder after hoisting.)- hoisting(호이스팅): var, function declaration 이 제일 위로 가는 것
- Sync
console.log('1');
console.log('2');
console.log('3');
// 브라우저 콘솔창에는 1, 2, 3 순서대로 나타난다
- Asynchronous(비동기) : 코드 블럭이 순차적으로 실행 되나 비동기 부분의 경우 브라우저에 요청만 하고 브라우저에 나타나는 시간이 달라 브라우저 상에서는 다른 순서로 출력 되는 경우
Async
setTimeout
: 콜백함수와 밀리세컨을 param으로 받고 시간이 밀리세컨 만큼 지났을때 콜백함수 호출- 1000 밀리세컨드(1초 = 1000ms) 이후에 function을 호출 -> 브라우저가 시간에 맞게 실행하도록 요청해 놓고 다음 코드로 일단 넘어감
- setTimeout 과 같은 브라우저 API들은 브라우저에게 먼저 요청을 보내게 되고 응답 안기다리고 다음 블럭으로 패스함
console.log('1');
setTimeout(() => console.log('2'), 1000); // 비동기 부분
console.log('3');
//
- 하지만, 항상 Callback 함수를 비동기시에만 사용하는 것이 아님
Callback Function
- Callback 함수도 sync, async 모두 있음
- Callback 함수 표현은 각 언어 마다 다름
- Synchronous callback
function printImmediately(print) {
print();
} // 이건 선언이니까 hoisting 되고
printImmediately(() => console.log('hello'));
- Asynchronous callback
function printWithDelay(print, timeout) {
setTimeout(print, timeout);
}
printWithDelay(() => console.log('async callback'), 2000); // 비동기
- 하지만, 이러한 콜백함수를 너무 남발해서 쓰면 안좋음!!
Callback Hell example (callback 함수 사용의 잘못된 예)
hell example
- 예제 구현사항
1) 사용자에게 id , password 받아오기
2) login 하기
3) login 성공시 Roles 가져오기
4) name: raccoon, role: admin 에서 가져온 name과 role값을 활용하여 로그인 성공 안내창 띄우기
- 예제 구현사항
- 예제 설명
UserStorage
클래스 생성loginUser
함수 : id, password를 받아 조건에 맞으면 성공 함수(받은 id를 활용), 안맞으면 실패함수(Error 처리) , 서버라고 생각하고 어느정도 시간 걸리게 함 (비동기)getRoles
함수 : user 를 받아 조건에 맞으면 성공함수(배열을 활용함), 그외 Error처리
- 연결)
prompt
함수를 통해 input 을 받아 id, password 할당- UserStorage 클래스를
userstorage
이름으로 instance화 시킴 - userstorage에서
loginUser
를 실행시킴 - loginUser의 각 param으로 들어가는 콜백함수를 가공활용하여 창 띄우는 기능을 구현하게 연결시킴
class UserStorage {
loginUser(id, password, onSuccess, onError) {
setTimeout(() => {
if (
(id === 'raccoon' && password === 'dream') ||
(id === 'coder' && password === 'academy')
) {
onSuccess(id);
} else {
onError(new Error('not found'));
}
}, 2000);
}
getRoles(user, onSuccess, onError) {
setTimeout(() => {
if (user === 'raccoon') {
onSuccess({ name: 'raccoon', 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)}
);
콜백 체인의 문제점
- 가독성 제로
- 에러발생 및 디버깅에서 어려움, 분석도 어렵고 유지보수도 어려움
그래서 Promise가 필요함
JavaScript Promise Object
Promise
- 비동기를 간편하게 처리할수 있도록 도와주고자 js에서 제공하는 Object 이다.
(promise is a JavaScript object for asynchronous operation.) - 실행 성공하면 처리된 결과값 전달, 문제 발생시 error 전달
- 공부 포인트
1) state (상태) : 기능실행중인지, 성공했는지, 실패인지
2) producer vs consumer
State (상태)
: pending(수행중) -> fulfilled(실행 성공) or rejected(파일 없거나, 네트워크 문제가 생기면)- promise Object의 활용법은 producer, consumer가 있음
producer
: 원하는 기능을 수행해 해당하는 데이터를 만들어 내는 promise Objectconsumer
: 원하는 데이터를 소비하는 promise Object
Producer
Promise
객체는 param으로executor 콜백
을 받음excutor 콜백
은 param으로resolve(정상 수행시)
,reject(실패시)
콜백함수를 받음
- when new promise is created, the executor runs automatically.
- promise를 만들자 마자 executor가 실행이 됨 -> 즉, 만들자마자 네트워크 통신을 시작함 (django view의 Request get느낌?)
- 네트워크 통신을 사용자가 요구할때만 해야 되는 경우 (사용자가 버튼을 눌렀을 때) 가 있기 때문에
- 불필요한 네트워크가 없게 유의하며 promise를 만들어야함
const promise = new Promise((resolve, reject) => {
// doing some heavy work (network, read files)
console.log('doing somthing...');
setTimeout(() => {
resolve('raccoon success!'); // 성공시 return 하는 값
// reject(new Error('no network')) // 실패시 return Error
}, 2000);
});
Consummers
- producer에서 정의한 데이터 로직들을 통해 출력을 어떻게 할지 정하거나, 오류 예외처리 및 유기적으로 연결하여 기능을 구현
then
함수 : promise 객체로직이 성공적으로 수행시 resolve의 return을 받아 param으로 활용하는 콜백 함수를 param을 가짐catch
함수 : promise 객체 로직에 erorr 가 생기거나, 성공하지 못하는 경우 (결국 그냥 reject 만나면)코드 실행을 멈추고 reject의 return을 받아 param으로 활용하는 함수를 param으로 가짐finally
함수 : 성공하던 실패하던 무조건 호출 되는 함수
promise
.then(value => {
console.log(value);
})
.catch(error => {
console.log(error); // error가 출력되지 않고 받아온 에러를 console.log에 출력됨을 알수 있음
}) // then을 호출하면 다시 promise를 호출하게 되고 return된 promise에 catch를 등록함
.finally(() => {console.log('finally')}) // 성공하든 안하든 무조건 호출
Promise chaning (프로미스 연결하기)
fetchNumber
이름의 promise 객체 생성- 해당 객체 접근하여 promise를 다루는 함수 then가 어떻게 동작하는지 보기
- producer 부분에서 resolve를 숫자 1로 넣음
- then 함수안의 콜백함수의 로직을 통해서 원하는 기능 구현
- then 함수들을 차례대로 나열하면서 기능 구현 (묶어서 가능하단 이야기)
"producer"
const fetchNumber = new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000);
});
"consumer"
fetchNumber
.then(num => num * 2) // then은 값을 전달해도 되고 다른 비동기 promise를 전달해도 됨
.then(num => num * 3)
.then(num => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(num -1), 1000);
});
})
.then(num => console.log(num)); // 5
// then들을 나열해서 다른 비동기적인 것을 묶어서 처리 가능
Error Handling (에러처리)
- producer part
const getHen = () =>
new Promise((resolve, reject) => {
setTimeout(() => resolve('🐓'), 1000);
});
const getEgg = hen =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(`${hen} => 🥚`), 1000);
// setTimeout(() => reject(new Error(`error ${hen} => 🥚`)), 1000); // error가 발생한 경우
});
const cook = egg =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(`${egg} => 🥮`), 1000);
});
consumer part
then을 통한 연쇄 가공, 활용
- then은 return된 resolve를 받아와 가공할수 있는 함수 이기 때문에
- then안의 콜백함수가 promise을 이용하면 return 되는 것을 계속 연결 시킬수 있음
- 즉, 연쇄적으로 가공, 활용이 가능하다는 것
getHen()
.then(hen => getEgg(hen))
.then(egg => cook(egg))
.then(meal => console.log(meal)) // 닭 => 계란 => 빵
.catch(console.log);
- 꿀팁) resolve받아와서 같은 이름의 param을 바로 또 다른 함수에 넣는 경우 앞에 param, Arrow 생략 가능
getHen()
.then(getEgg)
.then(cook)
.then(console.log) // 닭 => 계란 => 빵
.catch(console.log);
- 에러 처리
catch
를 통해서 에러 처리를 함 catch에서 발견할 수 있는데, 해당producer part
에서 어떤 에러인지 알기위해서 처리가 필요함reject를 가공
해서 Error를 일으키고 error안내를 설정할 수 있다.
const getEgg = hen =>
new Promise((resolve, reject) => {
// setTimeout(() => resolve(`${hen} => 🥚`), 1000);
setTimeout(() => reject(new Error(`error ${hen} => 🥚`)), 1000); // 의도 하지 않은 것은 error를 일으켜 error처리 가능하고 reject의 콜백함수를 통해 error 메세지 가공이 가능
});
- Error를 그대로 가져오면 코드 실행이 멈추기 때문에 consumer part에서 then 과정에서 error가 날수 있는 부분에
catch안의 콜백함수
를 통해서 받아오는 error를 다른 정상적인 값을 호출시켜 전체 프로세스에 영향을 주지 않고 값을 내게 만들수 있음 - 주의) error 발생후 error를 바꿔서 return 시켜도 error 이전의 것은 모두 없어지는 것이라서 return 값 부터 반영되어 최종 값을 만들어냄
getHen()
.then(hen => getEgg(hen))
.catch(error => {
return '😅'; // egg 받아오는게 문제가 생겨도 전체적이 프로세스는 진행이 되게 하여
}) //error 발생 자체를 resolve로 다른것을 return 하게해서 진행시킴)
.then(egg => cook(egg))
.then(meal => console.log(meal)) // 웃음 => 빵
.catch(console.log);
Callback hell -> Promise 바꾸기
class UserStorage {
loginUser(id, password) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (
(id === 'raccoon' && password === 'dream') ||
(id === 'coder' && password === 'academy')
) {
resolve(id);
} else {
reject(new Error('not found'));
}
}, 2000);
})
}
getRoles(user) {
return new Promise ((resolve, reject) => {
setTimeout(() => {
if (user === 'raccoon') {
resolve({ name: 'raccoon', 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(userStorage.getRoles)
.then(user => alert(`Hello ${user.name}, you have a ${user.role} role`))
.catch(console.log);
- 뭐, 엄청 크게 달라지진 않고 comsumer part가 좀더 이해하기 쉽게 바뀌고 깔끔함
- 더 좋은 방식은 다음 시간에 알아보자~