Polling 구현

Taemin Jang·2024년 8월 8일
0

회사에서 인사 동기화를 구현하는 과정에서 인사 정보가 동기화되었는지 주기적으로 확인해야하는 상황이 생겼습니다.

간단한 프로세스는 인사 정보를 매핑하고, 수동 인사 동기화를 요청합니다.

요청 받은 서버는 수동 인사 동기화를 진행을 시작하고,

요청에 대한 반환값으로는 진행 중이면 P, 동기화가 완료되면 S, 실패하면 E를 반환합니다.

이때 주기적으로 확인해야할 필요가 있으므로 Polling 구현해서 이를 해결했습니다.

Polling이란?

이전에 웹소켓을 공부할 때 잠깐 알아본 기억이 있었는데, 다시 한 번 알아보겠습니다.

폴링은 클라이언트가 일정 주기로 서버에 필요한 데이터를 요청하는 것을 의미합니다.

JS에서는 setTimeout, setInterval 등으로 이를 구현할 수 있습니다.

변경사항이 없어도 계속 요청을 보내기 때문에 서버에 부담을 줄 수 있고, 요청하는 주기가 짧아질수록 부하는 커지며, HTTP Connection을 맺기 위한 비용이 계속 발생합니다.

Long Polling

클라이언트가 일정 주기마다 요청을 보내지만 서버가 응답을 바로 전달하지 않는 방식을 의미합니다.

  1. 클라이언트가 서버에게 요청을 보냅니다.
  2. 서버는 즉시 요청을 처리하고 응답을 보내지 않습니다.
  3. 서버에서 특정 이벤트가 발생하면 응답을 보내고, 서버의 응답이 없으면 타임 아웃이 발생합니다.
  4. 응답을 받은 클라이언트는 다시 서버에게 요청합니다.

롱 폴링으로 처리하면 폴링의 단점을 보완할 수 있습니다.

그런데도 폴링으로 하게 된 이유는 폴링은 서버에서 별도의 추가적인 작업이 필요없지만, 롱 폴링은 서버에서 추가적인 작업이 필요합니다.

해당 요청을 즉시 처리하지 않기 때문입니다.

따라서 추가적인 작업이 필요없는 폴링 방식을 선택하게 되었습니다.

Polling 구현

위에서 말했듯이 setTimeout, setInterval으로 구현할 수 있습니다.

setTimeout

const timeoutPolling = (func, timeout) => {
  setTimeout(() => {
    func();
    timeoutPolling(func, timeout);
  }, timeout);
}

timeoutPolling(() => console.log('hi'), 1000)

timeoutPolling함수를 재귀적으로 호출하게 됩니다.

setTimeout 함수는 콜백 함수 실행 시간과 상관없이 콜백 함수 실행 간격이 일정하게 보장됩니다.
즉, 콜백 함수가 끝난 시점을 기준으로 시간을 체크합니다.

setInterval

const intervalPolling = (func, interval, maxAttempts = -1) => {
  let attempts = 0;
  let intervalId = setInterval(() => {
    if (maxAttempts === attempts) {
      clearInterval(intervalId);
      return;
    }
    attempts++;
    func()
  }, interval);
};

setInterval 함수는 콜백 함수의 실행 시간이 길면 콜백 함수 실행 간격이 짧아지게 됩니다.
즉, 콜백 함수가 시작한 시점을 기준으로 시간을 체크합니다.

while loop

setTimeout, setInterval 말고 while loop를 사용하는 방법도 있습니다. JS에는 타이머가 따로 제공되지 않기 때문에 Promise를 사용해서 직접 구현해야 합니다.

const delay = (timeout = 1000) => {
  return new Promise((resolve) => {
    setTimeout(resolve, timeout);
  });
};

const delayPolling = async (func, validateFunc, timeout) => {
  let result = await func();
  while (!validateFunc(result)) {
    await delay(timeout);
    try {
      result = await func();
    } catch (e) {
      console.error(e.message);
    }
  }
  return result;
};

이 중 어떤 방식을 선택해도 상관없지만, 익숙한 방식인 while loop로 진행했습니다.

const DEFAULT_TIME = 1000

const delay = (timeout = DEFAULT_TIMEOUT) => {
	return new Promise((resolve) => {
		setTimeout(resolve, timeout)
	})
}

const polling = async (callback: Function, validate: (status: string) => boolean, timeout: number) => {
	let result = await callback()
	while (!validate(result)) {
		await delay(timeout)
		result = await callback()
		if (result === 'E') {
			throw Error('status')
		}
	}
	return result
}

const hrInfoSyncMapping = async () => {
	const getSyncStatus = async () => {
		const key = '/api~'
		try {
			const data = await $http.post(key, {
				body: {
					...
				}
			})
			if (data.code === 200) {
				return data.data.status
			}
		} catch (error) {
			console.error('--- 인사 정보 상태 catch error', error)
		}
	}

	const isNextStep = (status: string) => {
		return status === 'S'
	}

	const resPolling = await polling(getSyncStatus, isNextStep, DEFAULT_TIMEOUT)
	
	...
profile
하루하루 공부한 내용 기록하기

0개의 댓글

관련 채용 정보