yonghyeun / maps-test

https://maps-test-chi.vercel.app
0 stars 0 forks source link

백그라운드 환경에서 lat , lon 확인하기 #3

Open yonghyeun opened 1 month ago

yonghyeun commented 1 month ago

sadas

'use client';

import { useEffect, useState } from 'react';
import styled from 'styled-components';

const StyledLog = styled.section({
  padding: '1rem',
  border: '1px solid white',
  minWidth: '200px',
  minHeight: '200px',
  overflowY: 'scroll',
});

type Geo = {
  lat: number;
  lon: number;
  time: string;
};

const GeoLog = () => {
  const [geoInfo, setGeoInfo] = useState<Geo[]>([]);

  useEffect(() => {
    const intervalId = setInterval(() => {
      window.navigator.geolocation.getCurrentPosition((position) => {
        const coord = position.coords;
        setGeoInfo((prev) => [
          ...prev,
          {
            lat: coord.latitude,
            lon: coord.longitude,
            time: new Date().toLocaleString('ko-KR', {
              hour: '2-digit',
              minute: '2-digit',
              second: '2-digit',
              hour12: false,
              timeZone: 'Asia/Seoul',
            }),
          },
        ]);
      });
    }, 1000);

    return () => {
      clearInterval(intervalId);
    };
  }, []);

  return (
    <StyledLog>
      <h1>GeoLog</h1>
      <ul>
        {geoInfo.map(({ lat, lon, time }) => (
          <li key={time}>
            lat : {lat} lon : {lon} time : {time}
          </li>
        ))}
      </ul>
    </StyledLog>
  );
};

export default GeoLog;

현재 이처럼 setIntervalwindow.navigator 를 이용하여 사용자의 좌표에 접근하는 방법을 이용해봤음

하지만 해당 방법은 문제가 있는데 브라우저마다 정책에 따라 다르겠지만 현재 구글 크롬에선 브라우저가 백그라운드 상태 일 때엔 setInterval 이 백그라운드에서 호출되는 것이 아닌 pending 상태로 있다가 다시 활성화 된 경우 pending 된 상태의 코드들을 일괄로 실행한다는 점임

만일 내가 1초마다 현재의 위치를 로그하기로 한 상태에서 5초까진 활성화 상태였다가 5초간 비활성화를 하게 되면

로그는 5초까진 실시간 로그가 찍히고

10초까진 로그가 찍히지 않다가 다시 활성화 된 상태를 기준으로 pending 상태였던 함수들이 모두 호출됨

1
2
3
4
5
/* pending 된 상태들이였던 함수들 */
10
10
10
10
10
/* 활성화 된 시점에 호출된 함수 */
10

이런식으로 말이다.

yonghyeun commented 1 month ago

좀 더 이러한 문제가 발생하는 이유에 대해 이론적으로 찾아왔다.

브라우저들은 최적화를 위해 브라우저가 최소화 되어 있을 때 메인스레드에 존재하는 excution 들을 모두 실행하지 않아둔다고 한다.

그렇기 때문에 이벤트 루프틀 건너 메인스레드(콜스택)에서 실행되는 setInterval 의 콜백함수들은 비활성화 상태에서 실행되지 않았다가 브라우저 활성화와 함께 한 번에 실행이 되었던 것이다.

이를 해결하기 위해 Web Worker 라는 기본 인스턴스가 존재한다는 사실을 발견했다.

Web Wrokers API - MDN

worker 에 대한 내용 요약은 다음과 같다.

제일 중요한 점은 worekr 에서 fetch api 사용이 가능하며 setInterval , setTimeout 등이 가능하단 것이다 호호호

yonghyeun commented 1 month ago

여러 실험을 해본 결과 근본적으로 메인스레드에서만 접근이 가능한 window.navigator.geolocation 은 백그라운드에서도 작동하는 worker 에서 사용이 불가능하다.

그래서 브라우저가 비활성화 된 상태에서 실시간 위치를 추적하는 행위는 근본적으로 불가능하다는 결론이 내려졌다.

이렇게 슬플 수가 없다 .,.

yonghyeun commented 1 month ago
'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 프로퍼티를 새롭게 추가함

idxnew Date().getTime() 으로 생성되기 때문에 절대적인 인덱스 지표가 될 수 있음

상태 업데이트 시 idx 를 이용하여 정렬하도록 하였기 때문에 비활성화 -> 활성화로 돌아와서 이전에 쌓여뒀던 setInterval 들이 뒤죽박죽 호출되어도 정렬된 위치 정보를 얻을 수 있을 것으로 기대됨

다음 코드로 배포해두고 동네에 아이스크림 사러 가기로 함 .. 냠냠