이 글은 browser-image-processing.tsImageCompressor 컴포넌트의 실패 가능 지점을 기준으로 작성했습니다. 브라우저 이미지 처리는 성공 경로보다 실패 경로를 먼저 정리해야 사용자 파일을 안전하게 다룰 수 있습니다.

문제: 파일 입력은 정상 이미지만 들어온다고 가정할 수 없다

input type="file"accept="image/*"를 넣어도 모든 문제가 사라지지는 않습니다. 브라우저가 선택 창에서 파일을 어느 정도 거를 수는 있지만, 파일이 손상됐거나 MIME type이 기대와 다르거나 이미지 디코딩이 실패할 수 있습니다. 사용자는 파일을 골랐을 뿐인데 도구가 멈추면 이유를 알 수 없습니다.

img-compressor의 이미지 처리 함수는 파일 읽기와 이미지 로딩을 Promise 흐름으로 감쌉니다. 이 구조는 성공 시 결과 파일을 반환하고, 실패 시 호출부가 오류를 감지할 수 있게 합니다. 실패를 조용히 무시하면 결과 목록이 비어 있는 이유를 설명할 수 없고, 사용자는 같은 작업을 반복하게 됩니다.

FileReader 단계의 실패

첫 번째 경계는 FileReader입니다. 브라우저는 선택한 파일을 Data URL로 읽어야 이미지 객체에 전달할 수 있습니다. 이 과정에서 읽기 오류가 발생하면 다음 단계로 넘어가면 안 됩니다. 빈 문자열이나 잘못된 Data URL을 이미지에 넣으면 이후 오류가 더 모호해집니다.

const reader = new FileReader();

reader.onerror = () => {
  reject(new Error('파일을 읽을 수 없습니다.'));
};

reader.onload = (event) => {
  if (!event.target?.result) {
    reject(new Error('이미지 데이터를 찾을 수 없습니다.'));
    return;
  }
  image.src = event.target.result;
};

오류 메시지는 개발자용 콘솔에만 남기기보다 사용자에게도 연결되어야 합니다. 다만 모든 내부 메시지를 그대로 노출할 필요는 없습니다. 사용자는 “이 파일은 처리할 수 없습니다. 다른 이미지로 다시 시도해 주세요.” 정도의 안내를 받으면 충분한 경우가 많습니다.

이미지 디코딩과 Canvas 단계의 실패

파일을 읽었다고 해서 이미지로 그릴 수 있다는 뜻은 아닙니다. Image 로딩이 실패할 수 있고, Canvas context를 얻지 못할 수도 있습니다. 또한 canvas.toBlob은 콜백을 호출하더라도 null을 전달할 수 있습니다. 이 세 지점은 각각 다른 실패 원인이므로 같은 성공 경로 안에 섞으면 안 됩니다.

const context = canvas.getContext('2d');
if (!context) {
  reject(new Error('Canvas context를 만들 수 없습니다.'));
  return;
}

canvas.toBlob((blob) => {
  if (!blob) {
    reject(new Error('이미지 변환 결과를 만들 수 없습니다.'));
    return;
  }
  resolve(new File([blob], fileName, { type: 'image/webp' }));
}, 'image/webp', quality);

실패 경로를 명확히 하면 테스트도 쉬워집니다. 손상 파일, 지원하지 않는 파일, 너무 큰 파일, 브라우저별 Canvas 동작을 따로 확인할 수 있습니다. 반대로 모든 오류를 하나의 catch에서만 처리하면 실제로 어느 단계가 약한지 알기 어렵습니다.

처리 실패와 완료 결과를 섞지 않는다

다중 파일 처리에서는 일부 파일만 실패할 수 있습니다. 이때 전체 작업을 무조건 중단할지, 성공한 파일은 결과 목록에 남길지 정책을 정해야 합니다. img-compressor 같은 브라우저 도구에서는 성공한 파일은 유지하고, 실패한 파일은 별도 안내로 표시하는 흐름이 사용자에게 더 유용합니다.

중요한 것은 실패한 파일을 성공 목록에 넣지 않는 것입니다. 다운로드 가능한 결과 카드에는 실제 파일 객체와 Object URL이 있어야 합니다. 실패한 항목을 빈 카드로 추가하면 사용자는 어떤 파일을 다시 시도해야 하는지 알 수 없고, 다운로드 버튼도 신뢰하기 어렵습니다.

Object URL 정리는 실패 경로에도 필요하다

미리보기와 다운로드 링크를 만들기 위해 URL.createObjectURL을 사용하면, 화면에서 항목을 제거할 때 URL.revokeObjectURL로 정리해야 합니다. 성공 경로만 생각하면 전체 삭제 버튼에서만 정리하면 될 것처럼 보입니다. 하지만 중간 변환이 실패했거나 개별 항목을 지웠을 때도 이미 만들어진 URL이 있다면 해제해야 합니다.

이 정리는 눈에 보이지 않는 안정성 작업입니다. 사용자가 몇 장만 처리하면 차이를 느끼기 어렵지만, 여러 파일을 반복해서 넣고 지우는 도구에서는 메모리 사용량과 브라우저 반응성에 영향을 줄 수 있습니다.

사용자에게 보여 줄 오류 문구 기준

  • 파일을 읽을 수 없는 경우: 파일 손상 또는 권한 문제 가능성을 안내합니다.
  • 이미지로 열 수 없는 경우: 지원하지 않는 형식일 수 있음을 안내합니다.
  • 변환 결과를 만들 수 없는 경우: 다른 품질값이나 더 작은 이미지를 시도하도록 안내합니다.
  • 일부 파일만 실패한 경우: 성공한 파일은 유지하고 실패 파일명만 따로 표시합니다.
  • 반복 실패가 발생한 경우: 브라우저 새로고침보다 원본 파일 확인을 먼저 제안합니다.

재심사 전 확인할 안정성 체크리스트

  1. 일반 사진, 스크린샷, 투명 PNG를 각각 처리해 결과 파일이 실제로 열리는지 확인합니다.
  2. 손상되었거나 이미지가 아닌 파일을 넣었을 때 화면이 멈추지 않는지 확인합니다.
  3. 여러 파일 중 하나가 실패해도 성공한 파일의 다운로드가 유지되는지 확인합니다.
  4. 개별 삭제와 전체 삭제 뒤 새 파일을 다시 넣어도 미리보기가 꼬이지 않는지 확인합니다.
  5. WebP 변환 결과가 더 커졌을 때 사용자에게 불리한 결과를 자동으로 제공하지 않는지 확인합니다.

원본 코드