이 글은 개인적인 공부의 목적으로 작성하는 글이므로 오류가 있을 수 있으니 참고해주시기 바랍니다!
자바스크립트는 싱글 스레드(single-threaded)기반의 언어이다.
싱글 스레드란, 호출한 함수들이 쌓이는 호출 스택(call stack)이 하나인 것을 의미한다. 즉, 이벤트를 처리하는 호출 스택이 하나뿐이어서 한번에 하나의 작업만 할 수 있다.
따라서 여러가지 이벤트를 처리할 때 동기적으로 하나씩 처리한 후 다음 이벤트로 넘어가게 된다면, 많은 시간이 소요되는 하나의 이벤트를 처리한다고 할 때 해당 작업이 끝날 때까지 다른 어떠한 작업도 처리할 수 없는 현상이 일어난다.
웹 상에서 필수적으로 쓰이는 자바스크립트에서 이렇게 한번에 하나의 작업만 하게된다면 이용자들은 매우 답답하지 않을까? 이를 해결하기 위해 자바스크립트에서는 이벤트 루프를 통한 비동기 작업처리를 지원한다.
이렇게 시간이 많이 걸리는 작업들을 비동기로 구현할 수 있게 되었지만, 만약 해당 비동기 작업들을 순차적으로 진행해야한다면 어떨까?
비동기로 구현되어 각 기능이 시간낭비 없이 동시에 실행되지만, 실행 결과의 순서가 보장되지 않는다는 단점이 있다.
따라서 비동기 작업의 순서가 보장되어야 할 때 아래의 방식들을 사용하여 문제를 해결할 수 있다.
- 콜백함수(Callback Function) 사용
- Promise 객체 사용
- async/await 사용
우선 이 포스트에서는 콜백함수를 사용한 비동기 처리에 대해 정리해보려고 한다.
다른 함수에 매개변수로 넘기는 함수. 이벤트가 발생할 때 또는 특정 시점에 실행되는 함수라는 의미에서 콜(call)백(back)함수라고 불린다.
function print(callback) {
callback();
}
위 코드에서 print()함수는 매개변수로서 또다른 함수(callback)를 받고있고 함수 내부에서 호출하고 있다. 여기서 매개변수로 전달된 함수를 콜백함수라고 한다.
setTimeout이라는 web API인 자바스크립트 내장 비동기 함수를 통해 비동기처리의 문제점을 예시로 들어보려고 한다. setTimeout은 두개의 인자를 받는데 첫번째 인자로 콜백함수를, 두번째 인자로 앞서 입력한 콜백함수를 실행하기 전 지연하는 시간을 밀리초 단위로 받는다.
아래는 데이터를 받아오는 과정을 setTimeout 함수를 이용하여 시뮬레이션한 내용이다.
function findUser(id){
let user;
setTimeout(function (){
user = {
id: id,
name: "User" + id,
email: id + "@test.com"
}
},1000);
return user;
}
const user = findUser(3);
console.log("user: ", user); //user: undefined
위 findUser함수에서 setTimeout 함수는 비동기 함수이므로 바로 web API로 함수가 넘어가게 된다.(이 부분은 추후 자바스크립트 이벤트 루프 관련하여 포스팅 예정입니다.) 이후에 리턴되는 user변수에는 아직 setTimeout 함수가 실행되지 않은 채 실행이 되기때문에 아무런 값도 할당되지 않게되어 결국 콘솔창에는 undefined가 출력되는 것이다. 모든 동기적 코드가 실행되고 나서 호출 스택이 비워져 그때서야 이벤트 루프가 비동기 함수를 실행하지만 이미 결과가 출력되고 난 이후의 상황이다.
이때 비동기함수를 실행하고 난 결과 값을 보장받기 위해 사용할 수 있는 가장 기본적인 방식이 콜백함수이다.
function findUser(id, callback) {
let user;
setTimeout(function () {
user = {
id: id,
name: "User" + id,
email: id + "@test.com",
}
callback(user)
}, 1000);
}
findUser(3, function (user) {
console.log("user:", user)
});
findUser함수로부터 결과 값을 바로 리턴하는 것이 아니라, 결과 값을 이용해 처리할 내용을 콜백함수에 작성하여 findUser의 매개변수로 전달한다. 이렇게 되면 콜백함수는 user변수에 값이 있을 때만 호출이 되기 때문에 우리가 원하는 값을 콘솔창에 출력할 수 있다!
참고자료로 보던 글 중 콜백함수의 사용을 아래와 같이 비유한 글을 보았는데 공부에 많이 도움이 되어 아래에 적어본다.
콜백 함수의 동작 방식은 일종의 식당 자리 예약과 같습니다. 일반적으로 맛집을 가면 사람이 많아 자리가 없습니다. 그래서 대기자 명단에 이름을 쓴 다음에 자리가 날 때까지 주변 식당을 돌아다니죠. 만약 식당에서 자리가 생기면 전화로 자리가 났다고 연락이 옵니다. 그 전화를 받는 시점이 여기서의 콜백 함수가 호출되는 시점과 같습니다. 손님 입장에서는 자리가 날 때까지 식당에서 기다리지 않고 근처 가게에서 잠깐 쇼핑을 할 수도 있고 아니면 다른 식당 자리를 알아볼 수도 있습니다. 자리가 났을 때만 연락이 오기 때문에 미리 가서 기다릴 필요도 없고, 직접 식당 안에 들어가서 자리가 비어 있는지 확인할 필요도 없습니다. 자리가 준비된 시점, 즉 데이터가 준비된 시점에서만 저희가 원하는 동작(자리에 앉는다, 특정 값을 출력한다 등)을 수행할 수 있습니다.
이렇게 콜백함수는 유용하게 사용되지만, 콜백함수만을 이용하여 비동기 처리를 순차적으로 진행하게될 경우 콜백함수 안에 콜백함수를 계속 호출하는 상황이 발생하며 이를 콜백지옥이라고 한다. 단어 그대로 지옥이라고 표현할 만큼 콜백함수의 중첩된 사용은 가독성과 유지보수성을 떨어트리기 때문에 이러한 코딩방식은 지양해야 한다.
이러한 콜백함수의 중첩된 사용으로 인한 단점을 보완하기 위해 ES6에 Promise객체가 새로 추가되었는데, Promise 객체에 대해서는 다음 포스팅에서 다뤄보려고 한다.