자바스크립트에서 프로그램의 실행을 지연시키기 (sleep)
코딩을 하다 보면 여러 가지 이유로 프로그램의 실행을 잠시 멈추거나 일정 시간동안 실행을 지연시키고 싶을 때가 있습니다.
예를 들어, 자바에서는 Thread.sleep()
함수, 파이썬에서는 time.sleep()
함수를 사용해서 이렇게 의도된 지연을 줄 수가 있습니다.
다른 프로그래밍 언어들에서는 이러한 API를 쉽게 찾아볼 수 있는데요.
자바스크립트에서는 어떻게 프로그램의 실행을 일정 시간동안 지연시킬 수 있을까요?
setTimeout() 함수
자바스크립트 API에서 다른 언어에서 제공하는 sleep
함수와 그나마 가장 유사한 것이 setTimeout()
함수입니다.
setTimeout()
함수는 두번째 인자로 넘어온 마이크로 초만큼 기다린 후, 첫번째 인자로 넘어온 함수를 호출해줍니다.
console.log("before");
setTimeout(() => console.log("after"), 3000);
위 코드를 브라우저 콘솔에서 실행해보면 before
가 먼저 출력되고, 3초 후에 after
가 출력될 것입니다.
before
after
하지만 실행을 지연시킨 코드 아래에 다른 코드를 추가하면 다음과 같이 (자바스크립트 초보자라면) 예상하지 못한 결과를 보게 됩니다.
console.log("before");
setTimeout(() => console.log("after"), 3000);
console.log("done!");
before
done!
after
왜, after
가 출력되기도 전에 done!
이 먼저 출력되는 것일까요?
기본적으로 자바스크립트의 실행 환경은 프로그램의 실행을 최대한 막지 않는(non-blocking) 방식, 즉 비동기(asynchronous)로 코드를 실행해줍니다. 쉽게 말해, 나중에 실행해야 할 코드는 옆으로 잠깐 치워놓았다가, 당장 실행이 가능한 다음 코드를 먼저 처리 후에, 다시 때가되면 원래 코드를 실행해줍니다.
이러한 자바스크립트의 비동기 프로그래밍 모델은 브라우저와 같이 리소스가 제한된 환경에서 성능 상의 큰 강점을 나타냅니다. 하지만 개발자 입장에서는 코드가 순서대로 실행되지 않는듯한 착시를 일으키기 때문에 자바스크립트를 배우려는 많은 분들에게 큰 걸림돌로 작용하기도 합니다.
setTimeout()
함수에 대한 좀 더 자세한 내용은 관련 포스팅을 참고바랍니다.
동기 지연
어떤 언어에서든지 프로그램의 실행을 멈추기 위해서 가장 단순하고 쉽게 생각할 수 있는 방법이 반복문을 사용하는 것입니다.
예를 들어, 일정 시간동안 프로그램의 실행을 멈추어주는 함수는 다음과 같이 자바스크립트로 작성할 수 있습니다.
function sleep(ms) {
const wakeUpTime = Date.now() + ms;
while (Date.now() < wakeUpTime) {}
}
이 함수는 인자로 넘어온 마이크로 초 동안 while
루프로 빈 반복문을 돌려줍니다.
간단한 테스트 함수를 작성 후에 실행해보겠습니다.
function test() {
console.log("before");
sleep(3000);
console.log("after");
}
> test()
before
after
before
가 콘솔에 출력된 후, 3초 후에 after
가 출력되는 것이 확인됩니다.
그 아래에 추가로 코드를 추가해도 예상했던 것처럼 순서대로 처리되는 것을 알 수 있습니다.
> test()
> console.log("done!")
before
after
done!
안타깝게도 자바스크립트에서 이렇게 동기적인(synchronous) 방식으로 코딩을 하게 되면 치명적인 성능 문제로 이어질 수 있습니다. 왜냐하면 싱글 쓰레드에서 이런 식으로 프로그램의 실행이 막히면 애플리케이션이 그 시간동안 아무것도 할 수 없는 상태가 되기 때문입니다. 예를 들어, 브라우저 환경에서 UI나 이벤트 처리 시에 이렇게 동기 지연을 걸면 사용자는 웹사이트가 응답하지 않는다고 생각하게 될 것입니다.
그래서 이 방법은 테스트 환경에서 일부로 이러한 병목 현상을 시뮬레이션하는 극히 제한된 용도로만 써야하며 상용 환경에서는 가급적 사용을 피해야합니다.
비동기 지연 - Promise
자바스크립트에서는 성능 상의 이유로 비동기로 프로그램의 실행을 지연시키는 것이 중요하다고 배웠는데요.
위에서 살펴본 setTimeout()
함수와 Promise를 함께 사용해서 지연 함수를 다시 작성해보았습니다.
function sleep(ms) {
return new Promise((r) => setTimeout(r, ms));
}
setTimeout()
함수를 직접 사용할 때는 콜백 함수를 넘겨야 했지만, Promise의 then()
함수를 사용할 수 있어서 좀 더 사용하기 편합니다.
function test() {
console.log("before");
sleep(3000).then(() => console.log("after"));
}
> test()
before
after
여기서 주의할 점은 비동기 방식으로 지연을 주기 때문에, setTimeout()
함수를 직접 사용했을 때 처럼 그 다음에 나오는 코드의 실행을 막지 않는다는 것입니다.
function test() {
console.log("before");
sleep(3000).then(() => console.log("after"));
console.log("done!");
}
> test()
before
done!
after
그 다음에 나오는 코드도 순서대로 실행이되길 원한다면, 다음과 같이 then()
함수를 연쇄 호출해주면 됩니다.
function test() {
console.log("before");
sleep(3000)
.then(() => console.log("after"))
.then(() => console.log("done!"));
}
> test()
before
after
done!
이 부분이 이해가 잘 되지 않으신다면 아래 자바스크립트의 비동기 처리 관련 포스팅를 먼저 읽어보시고 돌아오시기를 추천드립니다.
비동기 지연 - async/await
자바스크립트에 비교적 최근에 추가된 async/await
키워드를 활용하면 마치 동기 프로그래밍 모델로 코딩을 하듯이 좀 더 직관적으로 위에서 작성한 지연 함수를 호출할 수 있습니다.
기존 test()
함수에 앞에 async
키워드를 사용하고, sleep()
함수 앞에 await
키워드를 사용해줍니다.
async function test() {
console.log("before");
await sleep(3000);
console.log("after");
}
> test()
before
after
async
키워드로 선언한 함수 내부에서는 지연이 발생하지만, 함수 외부에서 실행되는 코드의 실행은 막히지 않는다는 점 주의 바랍니다.
> test()
> console.log("done!");
before
done!
after
그 다음에 나오는 코드도 순서대로 실행이되길 원한다면, 다음과 같이 test()
함수 앞에 await
키워드를 붙여서 호출해주면 됩니다.
> await test()
> console.log("done!");
before
after
done!
라이브러리 - waait
본인이 속한 프로젝트에서 이렇게 일정 시간을 기다렸다가 다시 실행해야하는 프로그램이 많을 경우에는 라이브러리를 사용하는 것도 고려해볼 수 있습니다.
대표적인 라이브러리로 waait를 들 수 있는데요, 소스 코드를 보시면 아시겠지만 위에서 작성한 코드와 크게 다르지 않습니다.
다음과 같이 waait
패키지로 부터 wait
함수를 임포트하여 우리가 직접 작성한 함수와 동일한 방식으로 사용하면 됩니다.
import wait from "waait";
async function test() {
console.log("before");
await wait(3000);
console.log("after");
console.log("done!");
}
> test()
before
after
done!
마치면서
지금까지 자바스크립트에서 프로그램의 실행을 지연시키기 위한 여러 가지 방법에 대해서 살펴보았습니다.
다시 처음으로 돌아가, 왜 자바스크립트는 다른 언어들처럼 지연 함수를 내장하고 있지 않을까요?
멀티 쓰레드 기반의 동기 프로그래밍 모델에서 프로그램의 실행 지연이 발생하면 일반적으로 CPU는 다른 쓰레드로 옮겨가 다른 코드를 실행하게 됩니다. 하지만 싱글 쓰레드 기반의 비동기 프로그래밍 모델에서는 잠시 동안 프로그램을 멈추는 행위 자체가 큰 위협이 되기 때문에 다른 관점으로 이를 바라봐야 합니다.
제 경험으로 비춰봤을 때, 정말로 프로그램의 실행을 지연시켜야하는 상황인건지를 신중하게 다시 한 번 생각해보는 것이 도움이 됩니다. 비동기 프로그래밍 모델 관점에서 바라보면, 많은 경우 더 좋은 해결 방법을 찾으실 수 있으실 것입니다.