기록 삭제와 파일 삭제를 나눈 이야기는 이미 남겼다. 이번에는 그보다 안쪽을 본다. 기록을 지운 직후 저장이 실패했을 때 화면의 행을 복구하는 일과, 다음 저장 요청이 계속 실행되게 하는 일은 같은 처리가 아니었다. 북극성 다운로드 저장 큐에서 그 두 책임을 따로 다뤘다.

배열을 고치는 일과 저장 순서를 지키는 일

다운로드는 새 항목 추가와 완료 갱신, 사용자의 기록 삭제가 겹칠 수 있다. 각각 비동기로 저장하면 먼저 시작한 오래된 목록이 나중에 저장돼 새 목록을 덮을 수 있다. BrowserHost는 downloadWrites라는 promise 꼬리에 저장을 붙여 순서를 만든다. 같은 상태를 동시에 저장하는 호출을 줄이는 장치다.

rememberDownloads는 큐의 차례가 왔을 때 현재 다운로드를 프로필별로 골라 최근 범위를 얕게 복사한다. 호출한 순간의 배열 참조를 그대로 저장기에 넘기는 것과 다르다. 실행 차례의 상태를 읽는 위치와 저장을 기다리는 순서가 함께 있어야 한다. 이 큐는 현재 프로세스의 저장 순서이며 다중 프로세스 트랜잭션 잠금은 아니다.

실패한 promise를 다음 작업의 입구로 남기지 않는다

promise의 then은 앞선 값이 거절되면 성공 콜백을 실행하지 않는다. 따라서 실패한 저장 promise를 그대로 꼬리에 두면 뒤의 저장도 자기 작업을 시작하지 못할 수 있다. rememberDownloads의 catch는 오류 안내를 남기고 상태 갱신을 알린다. 큐의 꼬리는 처리된 상태로 이어져 다음 저장이 들어갈 수 있다.

오류를 잡았다는 이유로 실패한 저장을 성공했다고 알리는 것은 아니다. downloadHistoryError에는 저장 위치를 확인하라는 안내가 남는다. 다음 저장이 성공하면 그 상태를 지운다. 다만 이 처리는 실패한 요청을 자동으로 무한 재시도하지 않는다. 이후 상태 변화의 저장과 처음 실패한 저장의 확인을 구별해야 한다.

삭제를 요청한 호출에는 실패를 그대로 돌려준다

clearDownloadHistory는 write라는 실제 삭제·저장 promise를 만들고, 큐의 꼬리에는 성공·실패 처리로 정리한 별도 promise를 넣는다. 호출자 쪽에서는 await write를 하기 때문에 저장 실패를 여전히 받는다. 다음 큐를 살리기 위해 오류를 처리하는 것과 현재 요청의 실패를 숨기는 것을 분리한 구조다.

finally에서는 화면 상태 변경을 알린다. 성공 때만 화면을 갱신하면 복구된 행과 오류 문구가 사용자에게 늦게 보일 수 있다. 시크릿 프로필은 영속 기록 저장을 건너뛰는 별도 조건을 유지한다. 모든 삭제가 같은 저장소에 확정된다고 설명하지 않고 어떤 프로필이 어떤 기록을 보관하는지 구분했다.

되돌릴 것은 이번에 뺀 행이지 옛 배열 전체가 아니다

removeDownloadHistory는 제거 대상 행만 보관하고 현재 배열에서 그 행을 뺀다. save가 실패하면 제거한 행 중 같은 id와 profileId가 아직 없는 것만 다시 넣고 시각 기준으로 정렬한다. 시작 때의 전체 목록을 덮어쓰면 기다리는 사이 새로 들어온 다운로드가 사라질 수 있다.

원본 시험은 저장 콜백 안에서 새 진행 기록을 추가한 다음 오류를 낸다. 복구 뒤에는 그 새 기록과 원래 기록, 다른 프로필의 같은 ID가 모두 남아야 한다. 이 부분은 앞선 글에서 소개한 복구 계약이며 이번 글에서는 큐의 실패 처리와 어떤 역할로 맞물리는지 설명한다. 같은 코드를 또 새 기능처럼 세지 않는다.

큐가 살아 있다는 사실과 저장 완료는 다르다

현재 설계는 한 번 실패한 저장 때문에 다음 요청 전체가 멈추는 일을 피한다. 하지만 큐가 다음 일을 받는 것만으로 마지막 목록이 디스크에 남았다고 증명할 수는 없다. 실제 저장 함수의 성공, 재시작 후 읽기, 오류 안내를 확인하는 일은 각각 다른 관찰이다. 끝낼 때 큐를 기다리는 코드도 그 일부다.

이번 집필에서는 현재 함수와 관련 반례 시험을 읽었으며 원래 앱의 실패를 새로 주입하지 않았다. 파일 영수증 합성 모듈 역시 이 promise 큐를 구현하지 않는다. 재사용할 때는 현재 호출자에게 돌려줄 실패와 다음 작업을 위해 처리할 실패를 구분해야 한다. 저장 실패를 알리면서도 다음 저장 요청을 처리할 수 있게 하는 방법을 다뤘다.