비동기식은 언제 실행이 될지, 또 언제 실행이 완료 될지 등을 알 수가 없다. 이를 위해서 callback을 인자로 받아서 특정 시점에(나중에) 실행해주겠다를 약속받을 수 있다. 이렇게 작성하는 것을 콜백 기반 비동기 프로그래밍이라고 한다.
비동기 로직을 실행한 이후 콜백으로 콜백을 받을 수 있는 비동기 로직을 넣을 수 있다. 이를 무한히 반복하게 되면.. ☠️
// 함수 정의
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(script);
document.head.append(script);
}
loadScript('/my/script.js', function(script) {
loadScript('/my/script2.js', function(script) {
loadScript('/my/script3.js', function(script) {
// 세 스크립트 로딩이 끝난 후 실행됨
});
})
});
에러가 발생했을 때의 동작을 외부에서 정의할 수 있도록 error값을 넣어줄 수도 있다.
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(null, script);
script.onerror = () => callback(new Error(`${src}를 불러오는 도중에 에러가 발생했습니다.`));
document.head.append(script);
}
loadScript('/my/script.js', function(error, script) {
if (error) {
// 에러 처리
} else {
// 스크립트 로딩이 성공적으로 끝남
}
});
이를 '오류 우선 콜백(error-first callback)'이라고 한다.
콜백 기반 비동이 처리를 무한히 반복하는 경우 콜백 지옥(callback hell)을 경험할 수 있다.
loadScript('1.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('2.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('3.js', function(error, script) {
if (error) {
handleError(error);
} else {
// 모든 스크립트가 로딩된 후, 실행 흐름이 이어집니다. (*)
}
});
}
})
}
});
이는 각각의 동작을 분리하고 함수로 만들면 이를 피할 수는 있다. 다만 함수가 여기저기 있다면 가독성에 치명적..
loadScript('1.js', step1);
function step1(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('2.js', step2);
}
}
function step2(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('3.js', step3);
}
}
function step3(error, script) {
if (error) {
handleError(error);
} else {
// 모든 스크립트가 로딩되면 다른 동작을 수행합니다. (*)
}
};
우리는 이를 해결하기 위해서 프로미스를 사용할것