이슈 배경
달리기 앱에서 타이머를 구현하고 사용하던 중 장시간 타이머를 사용시 실제시간과 타이머 표시시간 사이에 오차가 발생하는 것을 알게 되었습니다.
이슈 원인 찾기
타이머 정확성 관련해서 구글링을 했고
javascript의 이벤트 루프와 taskqueue 그리고 promise와 같은 비동기 처리 과정에서 발생하는 지연 문제임을 파악했습니다.
🔍 이벤트 루프와 마이크로 태스크 큐, 매크로 태스크 큐 란?
이벤트 루프(Event Loop)
- 이벤트 루프는 자바스크립트의 비동기 처리 메커니즘의 핵심 개념입니다.
- 이벤트 루프는 콜 스택, 웹 API, 태스크 큐(microtask queue, macrotask queue)를 관리하며, 이들 간의 상호작용을 통해 자바스크립트 코드를 실행합니다.
- 이벤트 루프는 끊임없이 실행 중이며, 콜 스택이 비어있을 때마다 태스크 큐에서 다음 작업을 가져와 실행합니다.
microtask queue
- microtask queue는 Promise의 then/catch/finally 콜백, MutationObserver 등의 마이크로태스크를 관리하는 큐입니다.
- 마이크로태스크는 매우 빠르게 실행되며, 콜 스택이 비어있을 때마다 microtask queue에서 작업을 가져와 실행합니다.
- microtask queue의 작업은 macrotask queue보다 우선순위가 높습니다.
macrotask queue
- macrotask queue는 setTimeout, setInterval, I/O 작업, 이벤트 핸들러 등의 매크로태스크를 관리하는 큐입니다.
- 매크로태스크는 상대적으로 오래 실행되며, 콜 스택이 비어있고 microtask queue가 비어있을 때 macrotask queue에서 작업을 가져와 실행합니다.
작동 순서
- 콜 스택에 함수가 쌓입니다.
- 함수 내에서 비동기 작업(setTimeout, Promise 등)이 발생하면 해당 작업은 웹 API로 전달됩니다.
- 웹 API에서 작업이 완료되면 해당 작업은 태스크 큐(microtask queue 또는 macrotask queue)로 전달됩니다.
- 콜 스택이 비어있을 때, 이벤트 루프는 태스크 큐를 확인합니다.
- 먼저 microtask queue에서 작업을 가져와 실행합니다.
- microtask queue가 비어있으면 macrotask queue에서 작업을 가져와 실행합니다.
- 이 과정을 반복하며 자바스크립트 코드를 실행합니다.
이슈 해결 과정
(before)
(after)
문제 해결을 위해 setInterval의 각 호출 시 setTime을 통한 증가 방식에서 벗어나, 각 호출마다 시작 시간과 현재 시간을 비교하여 시간을 설정하는 방식으로 변경했고, 이를 통해 시간의 정확성을 향상 시켰습니다.
또한 더욱 정확한 시간 계산을 위해 호출 간격을 500ms로 줄였습니다. 하지만, 이 과정에서 useState
로 상태를 관리하게 되면 렌더링이 과도하게 발생하여 성능 저하의 우려가 있었습니다.
따라서 변수를 생성하여 실제 시간 값은 렌더링에 영향을 주지 않는 방식으로 하되, 1초이상 차이 날 경우에만 useState
를 통해 상태를 업데이트하고 화면에 반영하도록 하여 성능을 고려했습니다.
참고