자바스크립트는 synchronous
이다 (동기식)
synchronous
: Hoisting 후 순서대로 코드 실행
👉 코드가 나타나는 순서대로 실행된다
console.log('1')
console.log('2')
console.log('3')
순서대로 실행되어서 1 2 3 순으로 출력됨
console.log('1')
setTimeout(() => console.log('2'), 1000)
console.log('3')
순서대로 실행되었지만, 1초 뒤에 실행하는 setTimeout
함수에 console.log('2')가 callback
되어 1 3 2 순으로 출력됨
class UserStorage {
loginUser(id, password, onSuccess, onError) {
setTimeout(() => {
if (
(id === 'ellie' && password === 'dream') ||
(id === 'coder' && password === 'academy')
) {
onSuccess(id);
} else {
onError(new Error('not found'));
}
}, 2000);
}
getRoles(user, onSuccess, onError) {
setTimeout(() => {
if (user === 'ellie') {
onSuccess({ name: 'ellie', role: 'admin' });
} else {
onError(new Error('no access'));
}
}, 1000);
}
}
const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = prompt('enter your passrod');
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);
}
);
콜백지옥이란 비동기 프로그래밍시 발생하는 문제로, 함수 안에서 또 함수를 호출하고... 의 반복인 함수를 말한다
보통 콜백지옥은 뒤집힌 C자 커브의 모양을 나타낸다
1. 가독성이 좋지 않음
2. 코드 수정이 어려움
아래는 콜백 지옥을 해결하기 위한 방법들이다
자바스크립트 안에 내장되어있는 오브젝트
비동기적인 것을 수행할 때, 콜백함수 대신 유용하게 쓰일 수 있다
1. state (상태) : pending -> fulfuilled or rejected
2. producer(정보 제공자)와 consumer(정보 소비자)의 차이
새로운 Promise가 만들어 질 때, 전달한 executor
콜백 함수가 바로 실행 된다
사용자가 버튼을 눌렀을 때, 네트워크 요청을 해야되는 경우 등 => 불필요한 통신이 일어날 수 있으니 사용 하지 않는 것을 권장
const promise = new Promise((resolve, reject) => {
// doing some heavy work (network, read files)
// 보통 무거운 작업들을 비동기적으로 받아옴(시간이 걸리기 때문)
setTimeout(() => {
1. resolve('ellie') // 성공했을 때
}, 2000)
2. reject(new Error('no network')) // 실패했을 때
})
Promise 안에는 executor
함수를 콜백 받고, 그 안에서 resolve
, reject
함수를 콜백 받는다
then, catch, finally
promise.then((value => { // Promise가 잘 전달되어서 받은 값을 then으로 전달
console.log(value)
})
.catch(error => { // 에러가 발생했을 때 어떻게 처리할 것인지
console.log(error)
})
.finally(() => { // 결과가 어떻든 수행 됨
console.log('finally')
})
const fetchNumber = new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000) // 1초 뒤에 1을 출력하게
})
fetchNumber
.then(num => num * 2) // resolve된 값에 2를 곱하고 = 2
.then(num => num * 3) // 위 then에 3을 곱하고 = 6
.then(num => {
return new Promise((resolve, reject) => { // 위 then에 다른 서버와 통신하는 새 Promise를 리턴
setTimeout(() => resolve(num - 1), 1000) // = 5
})
})
.then(num => console.log(num)) // 위 동작을 다 실행하고 난 뒤에 실행 = 3초 뒤에 5 출력
const getHen = () =>
new Promise((reslove, reject) => {
setTimeout(() => resolve('🐔'), 1000)
})
const getEgg = hen =>
new Promise((reslove, reject) => {
setTimeout(() => 1. resolve(`${hen} => 🥚`), 1000)
// 네트워크에 문제가 생겼다면?
2. reject(new Error(`error! ${hen} => 🥚`), 1000)
})
const cook = egg =>
new Promise((reslove, reject) => {
setTimeout(() => resolve(`${egg} => 🍳`), 1000)
})
getHen()
.then(getEgg) // (hen => getEgg(gen))
4. .catch(error => { // 계란을 받아올 때 문제가 생긴다면,
return '🥖' // 대신 빵을 받아오겠다
})
.then(cook) // (egg => cook(egg))
.then(console.log) // (meal => console.log(meal))
3. .catch(console.log)
정상 실행 됐을 때 3초 뒤 결과값
🐔 => 🥚 => 🍳
에러가 생겼을 때의 결과값
에러가 생겼을 때, 마지막 줄에 catch
를 추가했을 때의 결과값 (어디에서 에러가 났는지 찾을 수 있음)
계란을 받아올 때 에러가 생겨서 계란은 못 받았지만, 대신 빵을 받아서 결과값인 cook을 출력할 수 있음
👉 Promise 체인이 실패하지 않고 오류가 잘 처리 되었다
순서를 변경해가며 어떻게 해야 오류가 안 날지 고민할 것
class UserStorage {
loginUser(id, password) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (
(id === 'ellie' && 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 === 'ellie') {
resolve({ name: 'ellie', role: 'admin' });
} else {
reject(new Error('no access'));
}
}, 1000);
});
}
const userStorage = new UserStorage();
const id = prompt('enter your id');
const password = prompt('enter your passrod');
userStorage
.loginUser(id, password)
.then(userStorage.getRoles)
.then(user => alert(`Hello ${user.name}, you have a ${user.role} role`))
.catch(console.log);
훨씬 짧고 가독성이 좋아진 것을 확인할 수 있다
Promise를 더 쉽고 깔끔하게 쓸 수 있음
async
코드를 함수 앞에 쓰면 자동으로 Promise
로 바뀌어서 인식한다
function fetchUser() {
return new Promise((resolve, reject) => {
// do network request in 10 secs...
resolve('ellie')
})
}
const user = fetchUser()
user.then(console.log)
console.log(user)
async function fetchUser() {
// do network request in 10 secs...
return 'ellie'
}
const user = fetchUser()
user.then(console.log)
console.log(user)
두 함수가 같은 동작을 하지만 밑에 async
함수가 더 짧음
await
은 async
가 사용된 함수에서만 쓸 수 있음
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
} // ms의 시간이 지나면 resolve를 리턴하는 함수
aysnc function getApple() {
await delay(3000) // 3초가 지나기까지 기다렸다가
return '🍎' // 사과를 리턴한다
}
aysnc function getBanana() {
await delay(3000) // 3초가 지나기까지 기다렸다가
return '🍌' // 바나나를 리턴한다
}
async function pinkFruits() {
const apple = await getApple()
const banana = await getBanana()
return `${apple} + ${banana}`
} // 6초가 지나면 사과와 바나나가 리턴된다
pickFruits.then(console.log)
기존 Promise 방식을 쓸 때보다 코드가 훨씬 간결해졌지만, 사과가 리턴 될 때까지 기다렸다가 바나나가 나올 필요가 없다면?
👉 병렬 방식을 이용한다
async function pinkFruits() {
const applePromise = getApple()
const bananaPromise = getBanana()
const apple = await applePromise()
const banana = await bananaPromise()
return `${apple} + ${banana}`
} // 3초가 지나면 사과와 바나나가 리턴된다
pickFruits.then(console.log)
Promise는 실행되는 순간 바로 값을 전달해주므로, 병렬 방식으로 사과와 바나나가 동시에 동작한다
그렇지만 코드가 지저분함
function pickAllFruits() {
return Promise.all([getApple(), getBanana()]).then(fruits =>
.then(fruits.join('+')
)
}
pinkAllFruits().then(console.log)
위 병렬 방식을 더 깔끔하게 사용하는 방법이다