유튜브 드림코딩 영상을 보고 정리한 내용입니다.
자바스크립트는 기본적으로 동기적으로 동작합니다. 호이스팅이 된 이후부터 우리가 작성한 순서대로 코드가 하나하나 실행되는데, 호이스팅은 변수나 함수 선언이 자동적으로 위로 올라가는 것을 말합니다. 아래처럼 콘솔로그를 작성하면 순서대로 숫자가 찍힙니다.
console.log("1");
console.log("2");
console.log("3");
만약 아래처럼 setTimeout 함수에 콜백함수를 넣어서 비동기적 실행을 한다면, 1, 3, 2 순서로 함수가 출력됩니다.
console.log("1");
setTimeout(function () {
console.log("2");
}, 1000);
console.log("3");
setTimeout 은 브라우저 api 이므로, 브라주에게 1초 뒤에 실행해달라고 요청하고 응답을 기다리지 않고 아래 명령으로 넘어갑니다. 이런 것이 비동기적인 실행방법입니다. 이때 setTimeout 의 첫번째 인자로 넣어준 함수를, 바로 실행하지 않고 이후에 1초 후에 실행해라, 다시 불러달라라는 의미로 콜백함수라고 부릅니다. 보통은 화살표 함수로 간결하게 작성합니다.
setTimeout(() => {
console.log("2");
}, 1000);
콜백이 비동기일때만 쓰이는 것은 아닙니다. 콜백을 파라미터 인자로 받아서 처리하는 함수가 아래처럼 있을 때,
console.log("1");
setTimeout(() => {
console.log("2");
}, 1000);
console.log("3");
// Synchronous callback
function printImmediately(print) {
print();
}
printImmediately(() => {
console.log("hello");
});
자바스크립트 엔진에 따라 함수 선언은 호이스팅 되므로 아래처럼 함수 선언을 위로 올리고 이후 아래 순서대로 실행되었을 것입니다.
function printImmediately(print) {
print();
}
console.log("1");
setTimeout(() => {
console.log("2");
}, 1000);
console.log("3");
// Synchronous callback
printImmediately(() => {
console.log("hello");
});
이제 비동기로 동작하는 방식을 살펴보면, 아래 추가된 함수는 콜백함수와 시간을 인자로 전달받아 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);
이렇게 되면, 함수 선언은 호이스팅 되고 남은 것들이 순서대로 실행됩니다. 이때 hello 는 콜백이지만 동기적으로 호출되고 2와 async callback 이 비동기적으로 호출됩니다. 이런 콜백함수를 계속 묶어나가서 쌓이게 되는 것을 콜백 지옥이라고 표현합니다.
(콜백 지옥 예시 보여주기)
아래 코드는 사용자에게 id 와 password 입력을 받아와서 해당 값으로 로그인을 하고, 이에 성공하면 사용자 id 를 받아서 그 id 로 역할을 요청해서 받아 옵니다.
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);
}
}
위와 같은 class 함수를 만들고 아래처럼 실행할 수 있습니다.
const userStorage = new UserStorage();
// 클래스를 만들고
const id = prompt("enter your id");
const password = prompt("enter your password");
// id, 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 와 async 를 통해 이를 해결합니다.