한번에 많은 대량의 데이터를 처리하게 되면 싱글 스레드 구조상 연산이 끝나기 전까지 웹이 먹통이 됩니다.
이런 상황에서 태스크의 분산 처리가 필요한데
requestIdleCallback을 통해 브라우저의 유휴시간에 분할 요청하여 대량 데이터 연산을 처리할 수 있습니다.
window.requestIdleCallback() - Web API | MDN
window.requestIdleCallback() 메서드는 브라우저의 idle 상태에 호출될 함수를 대기열에 넣습니다. 이를 통해 개발자는 애니메이션 및 입력 응답과 같은 대기 시간이 중요한 이벤트에 영향을 미치지 않
developer.mozilla.org
우리의 목표는 다음과 같습니다
- 대량의 데이터를 처리하되 인터렉션을 유지한다.
- 데이터 처리 진행율을 시각화한다.
이를 구현하기 위해 작업들을 저장하고 작업의 처리 순서를 보장하기위해 선입선출의 Queue 자료구조가 필요합니다.
array를 통해 간단히 구현할 수 있지만 Array.shift에는 성능상의 이유로 아래와 같이 Queue클래스를 정의했습니다.
export default class Queue<T> {
private items: T[]
private index = 0
constructor() {
this.items = []
}
// 큐에 요소 추가
enqueue(element: T): void {
this.items.push(element)
}
// 큐에서 요소 제거
dequeue(): T | undefined {
if (this.isEmpty()) {
throw new Error('Queue is empty')
}
const result = this.items[this.index]
this.index++
return result
}
// 큐의 첫 번째 요소 반환 (제거하지 않음)
peek(): T | undefined {
if (this.isEmpty()) {
throw new Error('Queue is empty')
}
return this.items[this.index]
}
// 큐가 비어있는지 확인
isEmpty(): boolean {
return this.items.length - this.index <= 0
}
// 큐의 크기 반환
size(): number {
return this.items.length
}
// 큐를 비움
clear(): void {
this.items = []
}
}
핵심은 dequeue부분인데 shift를 우회하기 위해 내부에서 index를 관리하고 있습니다.
진행할 예제는 다음과 같니다.
길이가 100만인 배열의 연산을 브라우저 유휴 상태에서 분할하여 진행하도록 합니다.
const result: number[] = []
const data = new Array(1_000_000).fill(0)
이렇게 준비된 data 배열에 인덱스 값을 기준으로 2배 곱하는 연산을 100만번 진행할 것입니다.
이를 위해 윗 단계에서 준비한 queue자료 구조에 task를 정의합니다.
for (let i = 0; i < data.length; i++) {
queue.enqueue({
execute: () => {
result.push(i*2)
},
})
}
task는 execute라는 함수가 정의된 객체입니다. execute안에 분할로 처리할 업무를 정의합니다.
const processChanges: IdleRequestCallback = deadline => {
const max = 1000
let count = 1
while (deadline.timeRemaining() > 0 && !queue.isEmpty() && count < max) {
count++
const task = queue.dequeue()
if (task) task.execute()
}
if (!queue.isEmpty()) {
requestIdleCallback(processChanges)
} else {
console.timeEnd()
}
}
requestIdleCallback(processChanges)
이번 포스팅에서 가장 핵심적인 내용입니다. requestIdleCallback은 브라우저 유휴상태에서 실행되며 예상되는 대기 시간을 deadline.timeRemaining을 통해 제공합니다. 이를 통해 잔여 시간이 존재하는 경우 큐에서 정의한 task를 처리할 수 있습니다.
여기에 한 가지 추가적 장치로 한번의 requestIdleCallback에 최대 몇 번의 task를 처리할 것인지를 정의했습니다.
이는 task의 볼륨에 따라 상황에 맞게 조절하면 됩니다. 경우에 따라 횟수가 아닌 작업 시간을 기준으로 제약을 걸 수 있겠습니다. 실제 이런 제약이 없는 경우 requestIdleCallback을 사용하더라도 일정시간 브라우저가 먹통이되는 것을 경험할 수 있기에 필요한 제약입니다.
결론
이 방법은 브라우저의 유휴상태에 커다란 연산을 분할 연산하는 방법임으로 한번에 연산하는 것에 비해서 전체 작업 시간은 길어질 수 있습니다. 가장 중요한 것은 사용자에게 끊김없는 경험을 제공할 수 있다는 것과 요청한 연산에 대한 처리 진행율을 시각적 피드백으로 전달할 수 있다는 점에 있습니다.
참고
'javascript' 카테고리의 다른 글
자바스크립트와 동시성 (1) | 2024.06.25 |
---|