Open yonghyeun opened 1 month ago
좀 더 이러한 문제가 발생하는 이유에 대해 이론적으로 찾아왔다.
브라우저들은 최적화를 위해 브라우저가 최소화 되어 있을 때 메인스레드에 존재하는 excution
들을 모두 실행하지 않아둔다고 한다.
그렇기 때문에 이벤트 루프틀 건너 메인스레드(콜스택)에서 실행되는 setInterval
의 콜백함수들은 비활성화 상태에서 실행되지 않았다가 브라우저 활성화와 함께 한 번에 실행이 되었던 것이다.
이를 해결하기 위해 Web Worker
라는 기본 인스턴스가 존재한다는 사실을 발견했다.
worker
에 대한 내용 요약은 다음과 같다.
window
와 다른 컨텍스트인 워커 스레드에서 작동하며 꼭 백그라운드 작업 뿐 아니라 무거운 작업을 워커 스레드에서 작동 하는 것도 가능하다. window
와 worker
의 데이터 교환은 메시지 시스템을 사용하며 양측 모두 postMessage
메소드를 통해 data
를 교환하는 것이 가능하다. worker
는 메인스레드에서 일어나는 작업이 아니기 때문에 사용하지 못하는 메소드들이 존재한다. 예를 들어 DOM
조작은 메인스레드에서만 가능하기 때문에 불가능하다. (worker
에서 사용 가능한 메소드들)DedicatedWorkerGlobalScope
객체이고 여러 스크립트에서 고유하는 워커의 경우엔 SharedWorkerGlobalScope
이다. 제일 중요한 점은 worekr
에서 fetch api
사용이 가능하며 setInterval , setTimeout
등이 가능하단 것이다 호호호
여러 실험을 해본 결과 근본적으로 메인스레드에서만 접근이 가능한 window.navigator.geolocation
은 백그라운드에서도 작동하는 worker
에서 사용이 불가능하다.
그래서 브라우저가 비활성화 된 상태에서 실시간 위치를 추적하는 행위는 근본적으로 불가능하다는 결론이 내려졌다.
이렇게 슬플 수가 없다 .,.
'use client';
import { useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
export const StyledLog = styled.section({
padding: '1rem',
border: '1px solid white',
minWidth: '400px',
minHeight: '400px',
maxHeight: '500px',
overflowY: 'scroll',
});
type Geo = {
lat: number;
lon: number;
time: string;
idx: number; // 실행 순서를 보장하기 위한 new Date.getTime() 값
cnt: number;
};
const GeoLog = () => {
const [geoInfo, setGeoInfo] = useState<Geo[]>([]);
const cnt = useRef<number>(0);
useEffect(() => {
if (typeof window === 'undefined') return;
const intervalId = setInterval(() => {
cnt.current = cnt.current + 1;
const time = new Date().toLocaleString('ko-KR', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
timeZone: 'Asia/Seoul',
});
const idx = new Date().getTime();
window.navigator.geolocation.getCurrentPosition((position) => {
const coord = position.coords;
setGeoInfo((prev) => {
const newCoords = [
...prev,
{
lat: coord.latitude,
lon: coord.longitude,
idx,
time,
cnt: cnt.current,
},
];
return newCoords.toSorted((prev, next) => prev.idx - next.idx);
});
});
}, 1000);
return () => {
clearInterval(intervalId);
};
}, []);
return (
<StyledLog>
<h1>GeoLog</h1>
<ul>
{geoInfo.map(({ lat, lon, time, idx, cnt }) => (
<li
key={idx}
style={{
margin: '10px',
padding: '10px',
border: '1px solid white',
}}
>
<p>
lat : {lat} lon : {lon}
</p>
<p>
time : {time} idx : {idx}
</p>
<p>cnt : {cnt}</p>
</li>
))}
</ul>
</StyledLog>
);
};
export default GeoLog;
실험하던 중 setInterval
내부에서 ref
객체로 생성되는 cnt
값은 이상하게 측정되었으나 new Date
로 생성한 time
은 어찌 저찌 잘 생성되는 모습을 확인함
브라우저 최소화 환경에서 setInterval
이 완벽하게 block
되는 것이 아니라 실행이 최소화 되는 것이라 간주하고 우선 구현하도록 함
다만 다시 활성화로 돌아 왔을 떄의 호출 순서가 매우 뒤죽박죽인 것을 감안하여 idx
프로퍼티를 새롭게 추가함
idx
는 new Date().getTime()
으로 생성되기 때문에 절대적인 인덱스 지표가 될 수 있음
상태 업데이트 시 idx
를 이용하여 정렬하도록 하였기 때문에 비활성화 -> 활성화로 돌아와서 이전에 쌓여뒀던 setInterval
들이 뒤죽박죽 호출되어도 정렬된 위치 정보를 얻을 수 있을 것으로 기대됨
다음 코드로 배포해두고 동네에 아이스크림 사러 가기로 함 .. 냠냠
현재 이처럼
setInterval
과window.navigator
를 이용하여 사용자의 좌표에 접근하는 방법을 이용해봤음하지만 해당 방법은 문제가 있는데 브라우저마다 정책에 따라 다르겠지만 현재 구글 크롬에선 브라우저가 백그라운드 상태 일 때엔
setInterval
이 백그라운드에서 호출되는 것이 아닌pending
상태로 있다가 다시 활성화 된 경우pending
된 상태의 코드들을 일괄로 실행한다는 점임만일 내가 1초마다 현재의 위치를 로그하기로 한 상태에서 5초까진 활성화 상태였다가 5초간 비활성화를 하게 되면
로그는 5초까진 실시간 로그가 찍히고
10초까진 로그가 찍히지 않다가 다시 활성화 된 상태를 기준으로
pending
상태였던 함수들이 모두 호출됨즉
이런식으로 말이다.