machine code는 CPU에 의해 직접적으로 연산이 될 수 있는 기계수준의 언어를 뜻한다.
일반적으로 비동기 함수는 콜스텍이 비워져 있을 때만 실행되므로, 동기적 함수가 실행할 때에는 비동기 함수가 리턴하는 결과물을 받아볼 수가 없다
예를들어, 비동기 작업을 포함하는 생성자 XMLHttpRequest을 예로 들어본다면
function getData (url) {
let result;
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.send();
xhr.onload = () => {
if(xhr.status === 200) {
result = JSON.parse(xhr.response);
}
}
return result;
}
console.log(getData("https://test.com");
function getData (url, callback) {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.send();
xhr.onload = () => {
if(xhr.status === 200) {
callback(JSON.parse(xhr.response));
}
}
}
getData("https://test.com", console.log);
콜백방식의 문제는, 만약에 이러한 비동기적인 처리가 연쇄적으로 발생해야 할 경우, 예를 들어 특정 작업이 끝난 후 또다른 비동기 작업을 해야 한다면 또다시 그 작업 내부에 콜백을 보내야 하므로 이른바 "콜백 헬" 이라는 상황이 발생하게 된다
또한, 콜백 패턴의 가장 심각한 문제는 에러처리가 어렵다는 것이다.
예를들어, 주로 에러를 잡기 위해 사용하는 패턴인 try ... catch 구문을 예시로 들자면
try{
setTimeOut(() => { throw new Error("error!"); }, 1000);
}
catch(err){
console.log(err);
}
error 전파는 콜스텍의 기준으로 에러를 발생시킨 컨텍스트로부터 하위로 전달된다.
하지만 setTimeOut은 이미 webAPI에 콜백을 전달하고 스텍에서 없어진 상태다.
따라서, 콜백함수가 실행컨텍스트를 만들고, 그것이 콜스텍에 넣어져서 실행될 때에는 이미 팝되어 사라진 상태고,
콜백함수가 throw를 통해 에러객체를 전달해도 setTimeOut은 그것을 받지 않는다.
catch는 이미 사라진 setTimeOut에 대해서 에러를 잡지 않았으므로 결과적으로 catch가 적용되지 않는것과 같다.
이처럼 콜백 패턴은 에러에 대한 핸들링이 어렵게 만드므로 지양해야 한다
ES6부터 표준화되어 도입된 생성자 함수 Promise로 객체를 생성하면 비동기적인 작업을 손쉽게 처리하도록 도와준다
이 프로미스 생성자는 콜백함수를 인자로 받는데, 이때 인자에는 자동적으로 resolve, reject 함수를 차례로 전달받는다.
이때 인자로 전달받게 되는 두 함수는 사실 Promise 생성자 함수에 static으로 정의되어 있는 함수이다.
두 함수의 처리에 따라서, 해당 프로미스 객체의 슬롯, PromiseState과 PromiseResult가 달라지며
해당 PromiseResult를 프로토타입 슬롯에 존재하는 메소드 "then"에 인자로 들어오는 콜백함수의 인자로 전달받는다.
만약 에러를 발생하여 reject가 실행된다면, catch에 첫번째 인자인 콜백함수의 인자로 해당 PromiseResult 슬롯의 값이 전달된다.
이렇게 then을 통한 처리를 이용하여 콜백지옥이 아닌 then 체이닝을 통한 비동기 처리들을 단계별로 정리가 가능하다
만약 resolve된 결과값을 얻고 싶다면 async ... await을 이용한 방식으로 비동기 작업이 마치기를 기다리게 만든 후 로직을 처리할 수도 있다.
참고로 async가 붙게 되는 함수는 자동적으로 리턴하는 결과값을 Promise 객체화 시켜서 리턴하므로, 사용시 주의를 요한다