13.5 IIFE와 비동기적 코드
6장에서 IIFE(즉시 호출하는 함수 표현식)에 대해 알아봤습니다.
IIFE를 이용해서 클로저를 만들 수 있다는 것도 알았습니다.
이제 IIFE로 비동기적 코드를 처리하는 중요한 예제를 하나 살펴봅시다.
이 예제는 14장에서 다시 살펴보게 될 겁니다.
IIFE를 사용하는 사례 중 하나는 비동기적 코드가 정확히 동작할 수 있도록 새 변수를 새 스코프에 만드는 겁니다.
5초에서 시작하고 카운트다운이 끝나면 “go”를 표시하는 고전적 타이머 예제를 만들어 봅시다.
이 코드는 자바스크립트의 내장 함수 setTimeout
을 사용합니다.
setTimeout
은 첫 번째 매개변수인 함수를 두 번째 매개변수인 밀리초만큼 기다렸다가 실행합니다.
예를 들어 1.5초 뒤에 hello를 출력한다면 다음과 같이 합니다.
setTimeout(function() {
console.log("hello");
}, 1500)
필요한 것은 다 알았으니 카운트다운 함수를 만들어 봅시다.
var i;
for (i=5; i>=0; i--) {
setTimeout(function() {
console.log(i===0 ? "go!" : i);
}, (5-i)*1000)
}
여기서 let 대신 var를 쓴 이유는 IIFE가 중요하던 시점으로 돌아가서 왜 중요했는지 이해하기 위해서입니다.
5, 4, 3, 2, 1, go!가 출력될 거라 예상했다면, 아쉽지만 틀렸습니다.
-1이 여섯 번 출력될 뿐입니다.
어떻게 된 걸까요?
setTimeout
에 전달된 함수가 루프 안에서 실행되지 않고 루프가 종료된 뒤에 실행됐기 때문입니다.
따라서 루프의 i는 5에서 시작해 -1로 끝납니다.
그리고 -1이 되기 전에는 콜백 함수는 전혀 호출되지 않습니다.
따라서 콜백 함수가 호출되는 시점에서 i의 값은 -1입니다.
let을 사용해 블록 수준 스코프를 만들면 이 문제는 해결되지만, 비동기적 프로그래밍에 익숙하지 않다면 아 되는구나, 하고 넘어가지 말고 이 예제를 정확히 이해해야 합니다.
좀 어려울 수도 있지만, 14장의 주제인 비동기적 실행을 이해하기 위해 꼭 필요합니다.
블록 스코프 변수가 도입되기 전에는 이런 문제를 해결하기 위해 함수를 하나 더 썼습니다.
함수를 하나 더 쓰면 스코프가 새로 만들어지고 각 단계에서 i의 값이 클로저에 캡처됩니다.
이름 붙은 함수를 쓰는 예제를 먼저 봅시다.
function loopBody(i) {
setTimeout(function() {
console.log(i===0 ? "go!" : i);
}, (5-i)*1000);
}
var i;
for (i=5; i>=0; i--) {
loopBody(i);
}
루프의 각 단계에서 loopBody 함수가 호출됩니다.
자바스크립트는 매개변수를 값으로 넘깁니다.
따라서 루프의 각 단계에서 함수에 전달되는 것은 i가 아니라 i의 값입니다.
즉 처음에는 5가, 두 번째에는 4가 전달됩니다.
같은 변수 이름 i를 썼지만, 이게 중요한 건 아닙니다.
중요한 것은 스코프 일곱 개가 만들어졌고 변수도 일곱 개 만들어졌다는 겁니다(하나는 외부 스코프, 나머지 여섯 개는 loopBody를 호출할 때마다).
하지만 루프에 한 번 쓰고 말 함수에 일일이 이름을 붙이는 건 성가신 일입니다.
익명 함수를 만들어 즉시 호출하는 IIFE를 사용하는 게 더 낫습니다.
이전 예제를 IIFE를 써서 고쳐 쓰면 다음과 같습니다.
var i;
for (i=5; i>=0; i--) {
(function(i) {
setTimeout(function() {
console.log(i===0 ? "go!" : i);
}, (5-i)*1000);
})(i)
}
괄호가 아주 많군요!
하지만 이 코드를 살펴보면, 매개변수 하나를 받는 함수를 만들어서 루프의 각 단계에서 호출한 것과 완전히 똑같음을 알 수 있습니다.
즉시 호출하는 함수 표현식을 나타내는 다음 예제를 보십시오.
블록 스코프 변수를 사용하면 스코프 하나 때문에 함수를 새로 만드는 번거로운 일을 하지 않아도 됩니다.
블록 스코프 변수를 사용하면 이 예제를 극도로 단순화할 수 있습니다.
for (let i=5; i>=0; i--) {
setTimeout(function() {
console.log(i===0 ? "go!" : i);
}, (5-i)*1000);
}
이번에는 for 루프 안에 let 키워드를 썼습니다.
let 키워드를 for 루프 바깥에 썼다면 똑같은 문제가 발생했을 겁니다.
let 키워드를 이런 식으로 사용하면 자바스크립트는 루프의 단계마다 변수 i의 복사본을 새로 만듭니다.
따라서 setTimeout
에 전달한 함수가 실행될 때는 독립 스코프에서 변수를 받습니다.