회사에서 인사 동기화를 구현하는 과정에서 인사 정보가 동기화되었는지 주기적으로 확인해야하는 상황이 생겼습니다.
간단한 프로세스는 인사 정보를 매핑하고, 수동 인사 동기화를 요청합니다.
요청 받은 서버는 수동 인사 동기화를 진행을 시작하고,
요청에 대한 반환값으로는 진행 중이면 P, 동기화가 완료되면 S, 실패하면 E를 반환합니다.
이때 주기적으로 확인해야할 필요가 있으므로 Polling 구현해서 이를 해결했습니다.
이전에 웹소켓을 공부할 때 잠깐 알아본 기억이 있었는데, 다시 한 번 알아보겠습니다.
폴링은 클라이언트
가 일정 주기로 서버에 필요한 데이터를 요청하는 것을 의미합니다.
JS에서는 setTimeout, setInterval 등으로 이를 구현할 수 있습니다.
변경사항이 없어도 계속 요청을 보내기 때문에 서버에 부담을 줄 수 있고, 요청하는 주기가 짧아질수록 부하는 커지며, HTTP Connection을 맺기 위한 비용이 계속 발생합니다.
클라이언트가 일정 주기마다 요청을 보내지만 서버가 응답을 바로 전달하지 않는 방식을 의미합니다.
롱 폴링으로 처리하면 폴링의 단점을 보완할 수 있습니다.
그런데도 폴링으로 하게 된 이유는 폴링은 서버에서 별도의 추가적인 작업이 필요없지만, 롱 폴링은 서버에서 추가적인 작업이 필요합니다.
해당 요청을 즉시 처리하지 않기 때문입니다.
따라서 추가적인 작업이 필요없는 폴링 방식을 선택하게 되었습니다.
위에서 말했듯이 setTimeout, setInterval으로 구현할 수 있습니다.
const timeoutPolling = (func, timeout) => {
setTimeout(() => {
func();
timeoutPolling(func, timeout);
}, timeout);
}
timeoutPolling(() => console.log('hi'), 1000)
timeoutPolling함수를 재귀적으로 호출하게 됩니다.
setTimeout 함수는 콜백 함수 실행 시간과 상관없이 콜백 함수 실행 간격이 일정하게 보장됩니다.
즉, 콜백 함수가 끝난 시점을 기준으로 시간을 체크합니다.
const intervalPolling = (func, interval, maxAttempts = -1) => {
let attempts = 0;
let intervalId = setInterval(() => {
if (maxAttempts === attempts) {
clearInterval(intervalId);
return;
}
attempts++;
func()
}, interval);
};
setInterval 함수는 콜백 함수의 실행 시간이 길면 콜백 함수 실행 간격이 짧아지게 됩니다.
즉, 콜백 함수가 시작한 시점을 기준으로 시간을 체크합니다.
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)
...