자바스크립트의 비동기 처리란 특정 코드의 연산이 끝날 때까지 코드의 실행을 멈추지 않고 다음 코드를 먼저 실행하는 자바스크립트의 특성을 의미합니다.
setTimeout은 비동기 처리를 구현할 수 있는 함수이다.
//Asynchronous(비동기식) setTimeOut
console.log(`1`);
setTimeout(() => console.log(`2`), 1000);
console.log(`3`);
// Synchronous(동기식) callback
function printImmediately(print) {
print();
}
printImmediately(() => console.log(`hello`));
// Asynchronous(비동기식) callback
function printWithDelay(print, timeout) {
setTimeout(print, timeout);
}
printWithDelay(() => console.log(`async callback`), 2000);
콜백 지옥은 비동기 처리 로직을 위해 콜백 함수를 연속해서 사용할 때 발생하는 문제이다.
$.get('url', function(response) {
parseValue(response, function(id) {
auth(id, function(result) {
display(result, function(text) {
console.log(text);
});
});
});
});
일반적으로 Promise
, Async
를 사용하는 방법이 있다. 만약 코딩 패턴으로만 콜백 지옥을 해결하려면 아래와 같이 각 콜백함수를 분리해주면 된다.
function parseValueDone(id) {
auth(id, authDone);
}
function authDone(result) {
display(result, displayDone);
}
function displayDone(text) {
console.log(text);
}
$.get('url', function(response) {
parseValue(response, parseValueDone);
});
1. Producer (생산자)
const promise = new Promise((resolve, reject) => {
// 시간이 오래 걸리는 무거운 처리 (network, read files ..)
console.log(`doing something...`);
setTimeout(() => {
resolve(`david`); // 성공 하면
// reject(new Error(`no network`)); // 실패 하면
}, 2000);
});
2. Consumer (소비자)
promise
.then((value) => {
console.log(value);
})
.catch(error => {
console.log(error);
})
.finally(() => { // 성공하든 실패하든 마지막에 실행됨
console.log(`finally`);
})
3. Promise chaining (연결하기)
const fetchNumber = new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000);
});
fetchNumber
.then(num => {
return num * 2; // > 2
})
.then(num => num * 3) // > 6
.then(num => { // > 5
return new Promise((resolve, reject) => {
setTimeout(() => resolve(num - 1), 1000);
});
})
.then(num => console.log(num)); // > 5
4. Error handlling
const getHen = () => {
return new Promise((resolve, reject) => {
setTimeout(() => resolve(`🐓`), 1000);
// setTimeout(() => reject(new Error(`error! 🐓`)), 1000);
});
};
const getEgg = hen =>
new Promise((resolve, reject) => {
// setTimeout(() => resolve(`${hen} => 🥚`), 1000);
setTimeout(() => reject(new Error(`error! ${hen} => 🥚`)), 1000);
});
const cook = (egg) =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(`${egg} => 🍗`), 1000);
// setTimeout(() => reject(new Error(`error! ${egg} => 🍗`)), 1000);
});
getHen()
.catch(error => {
console.log(error);
return `💀`; // 빵꾸처리
})
.then(hen => getEgg(hen))
.catch(error => {
console.log(error);
return `💀`; // 빵꾸처리
})
.then(egg => cook(egg))
.catch(error => {
console.log(error);
return `💀`; // 빵꾸처리
})
.then(result => console.log(result))
.catch(error => {
console.log(error);
})
// 정직한 코드
// getHen()
// .then((hen) => {
// return getEgg(hen);
// })
// .then((egg) => {
// return cook(egg);
// })
// .then((result) => {
// console.log(result);
// })
// 파라미터가 1개일때 함수이름만 쓰면, 암묵적으로 함수의 매개변수로 전달 됨.
// getHen()
// .then(getEgg)
// .then(cook)
// .then(console.log);
비동기 처리를 반드시 해야하는 이유
// 프로미스를 사용하면 반드시 resolve와 reject를 호출해야 함
function fetchUser() {
return new Promise((resolve, reject) => {
// return `david`; // 프로미스 pending 상태
resolve(`david`); // 프로미스 fulfilled 상태
// reject(new Error(`error`)); // 프로미스 rejected 상태
});
}
const user = fetchUser();
// console.log(user);
user.then(user => console.log(user));
// 1. 함수 선언식
async function fetchUser() {
return `david`;
}
// 2. 함수 표현식
const fetchUser = async function() {
return `david`;
};
// 3. 화살표 함수
const fetchUser = async () => {
return `david`;
};
// fetchUser().then(data => console.log(data)); // 함수로 바로 호출
const user = fetchUser(); // 변수에 할당해서 호출
user.then(data => console.log(data));
console.log(user);
function delay(ms) {
return new Promise (resolve => setTimeout(resolve, ms));
}
function getApple() {
return delay(1000)
.then(() => `🍎`);
}
function getBanana() {
return delay(1000)
.then(() => `🍌`);
}
function pickFruits() {
return getApple()
.then(apple => {
return getBanana().then(banana => `${apple} + ${banana}`);
});
}
pickFruits().then(result => console.log(result));
function delay(ms) {
return new Promise (resolve => setTimeout(resolve, ms));
}
async function getApple() {
await delay(1000);
// throw new Error(`error: apple`); // error 발생
return `🍎`;
}
async function getBanana() {
await delay(1000);
// throw new Error(`error: banana`);
return `🍌`;
}
async function pickFruits() {
let apple = null;
try {
apple = await getApple();
} catch(error) {
console.log(error);
}
let banana = null;
try {
banana = await getBanana();
} catch(error) {
console.log(error);
}
return `${apple} + ${banana}`;
}
pickFruits().then(result => console.log(result));
function delay(ms) {
return new Promise (resolve => setTimeout(resolve, ms));
}
async function getApple() {
await delay(1000);
return `🍎`;
}
async function getBanana() {
await delay(1000);
return `🍌`;
}
// 방법 1: 무식한 코드
async function pickFruits() {
// 프로미스 객체는 선언 즉시 바로 실행됨
// getApple과 getBanana는 병렬(독립적)로 실행됨
const applePromise = getApple();
const bananaPromise = getBanana();
// 동기화
const apple = await applePromise;
const banana = await bananaPromise;
return `${apple} + ${banana}`;
}
pickFruits().then(result => console.log(result));
// 방법 2: Promise APIs 사용
function pickAllFruits() {
return Promise.all([getApple(), getBanana()]).then(fruits => {
return fruits.join(` + `);
});
// return Promise.all([getApple(), getBanana()]);
}
pickAllFruits().then(console.log);
// 번외: 비동기 처리중 먼저 리턴하는 녀석만 출력
function pickOnlyOne() {
return Promise.race([getApple(), getBanana()]);
}
pickOnlyOne().then(console.log);