Node 에서 재귀 크롤을 이벤트 루프에 맡기기
종료 감지를 큐의 onIdle() 에, 중복 제거의 원자성을 단일 스레드에 맡기면 in-flight 카운터와 락이 사라진다.
Node 로 링크를 따라가며 페이지를 재귀 크롤할 때 꺼낸다. 처리 결과(새 링크)가 다시 입력으로 돌아가서 일이 언제 바닥날지 미리 모르는 경우다.
import PQueue from 'p-queue'
const queue = new PQueue({ concurrency: 10 }) // 동시 요청 수만 제한한다. add 는 막히지 않는다(무제한 대기열)
const seen = new Set()
function enqueue(url) {
if (seen.has(url)) return
seen.add(url) // has 와 add 사이에 await 가 없으니 다른 코드가 끼어들 수 없다 — 락이 필요 없다
queue.add(() => visit(url)) // 요청보다 먼저 표시한다. 요청 뒤에 표시하면 그 사이 같은 URL 이 또 들어온다
}
async function visit(url) {
const res = await fetch(url) // HEAD 가 아니라 GET — 링크를 더 찾으려면 본문이 있어야 한다
const html = await res.text()
for (const link of extractLinks(html, url)) enqueue(link) // 이 태스크가 끝나기 전에 자식을 넣는다
}
enqueue(startUrl)
await queue.onIdle() // 대기열이 비고 실행 중인 태스크도 0 일 때 resolve
extractLinks 는 HTML 에서 같은 사이트 링크를 뽑는 함수(자리표시자). 서로 가리키는 페이지가 있어도 onIdle() 이 resolve 되고 seen.size 가 고유 URL 수와 같으면 된 것이다.
- 출처: How Other Link Checkers Do Recursion — linkinator(
onIdle·GET)·broken-link-checker 분석