API 스펙은 MDN FileReader 문서를 기준으로 확인했고, 실제 선택 이유는 img-compressor의 browser-image-processing.ts 코드를 근거로 설명합니다.

FileReader가 필요한 이유

<input type="file">로 사용자가 고른 값은 File 객체입니다. FileBlob을 상속하는데, 이름과 크기 같은 메타데이터는 바로 읽을 수 있지만 실제 내용은 동기적으로 접근할 수 없습니다. FileReader는 이 내용을 비동기로 읽어, 완료되면 onload 이벤트로 결과를 전달하는 브라우저 내장 객체입니다. 서버 업로드 없이 브라우저에서 파일을 미리보기하거나 가공하려는 도구는 대부분 이 지점에서 시작합니다.

네 가지 read 메서드와 결과 타입

메서드결과 타입대표 용도
readAsDataURL(file)Data URL 문자열 (base64)이미지 미리보기, <img src>에 바로 대입
readAsArrayBuffer(file)ArrayBuffer바이너리 파싱, WebAssembly·Web Audio API 입력
readAsText(file)문자열CSV, JSON, 텍스트 파일 파싱
readAsBinaryString(file)바이너리 문자열레거시 API. 표준 문서는 readAsArrayBuffer를 권장합니다.

네 메서드 모두 완료 시 onload, 실패 시 onerror, 진행 중에는 onprogress 이벤트를 발생시키는 동일한 이벤트 모델을 씁니다. 차이는 오직 event.target.result에 어떤 타입의 값이 들어오는지입니다.

img-compressor는 왜 readAsDataURL을 선택했나

이미지를 Canvas에 그리려면 Image 엘리먼트의 src에 문자열을 대입해야 합니다. Data URL은 data:image/png;base64,... 형태의 완결된 문자열이라 src에 그대로 넣을 수 있어, FileReader → Image → Canvas로 이어지는 파이프라인에 추가 변환 없이 맞아떨어집니다.

const reader = new FileReader();

reader.onload = (event) => {
  const image = new Image();
  image.onload = () => {
    // canvas.drawImage(image, ...) 로 이어짐
  };
  image.onerror = () => reject(new Error('Failed to load image'));
  image.src = event.target?.result as string;
};

reader.onerror = () => reject(new Error('Failed to read file'));
reader.readAsDataURL(file);

같은 목적을 URL.createObjectURL(file)로도 달성할 수 있습니다. 두 방식의 실제 차이와 img-compressor가 결과 미리보기에는 Object URL을 쓰면서 Canvas 입력에는 Data URL을 쓰는 이유는 Data URL과 Blob URL 비교 글에서 다룹니다.

실패는 onerror에서, 성공은 onload에서만 처리한다

FileReader는 이벤트 기반이라 try/catch로 실패를 잡을 수 없습니다. 읽기 실패, 권한 문제, 손상된 파일은 모두 onerror로 전달되므로, Promise로 감싸는 코드라면 onerror에서 반드시 reject를 호출해야 호출부가 실패를 인지할 수 있습니다. 이 실패가 이미지 도구 전체 흐름에서 어떻게 처리되는지는 에러 핸들링 가이드에 정리했습니다.

readAsArrayBuffer가 더 나은 경우

Data URL로 바꿀 필요 없이 원시 바이너리 자체가 필요한 상황도 있습니다. 예를 들어 media-crop은 영상 파일을 FFmpeg.wasm에 넘길 때 FileReader를 직접 쓰지 않고, @ffmpeg/utilfetchFile 헬퍼로 파일을 바이너리로 변환해 전달합니다.

import { fetchFile } from '@ffmpeg/util';

await ffmpeg.writeFile(inputName, await fetchFile(file));

이 사례는 “파일을 바이너리로 다뤄야 할 때 항상 FileReader를 직접 써야 하는 것은 아니다”라는 점을 보여 줍니다. WebAssembly 런타임처럼 이미 파일을 바이너리로 받는 유틸리티가 있다면, readAsArrayBuffer를 직접 호출하기보다 그 유틸리티에 위임하는 편이 코드가 단순해집니다. 반대로 이런 유틸리티가 없고 직접 바이트를 다뤄야 한다면 readAsArrayBuffer가 표준적인 선택입니다.

확인해 봐야 할 것

FileReader를 처음 도입할 때는 정상 파일보다 애매한 입력에서 먼저 문제가 드러납니다. 빈 파일, 확장자와 실제 내용이 다른 파일, 매우 큰 파일에서 onload가 언제 끝나는지, onerror가 실제로 발생하는지 직접 넣어 확인하는 편이 안전합니다. 관련 구현은 이미지 처리 파이프라인에서 그대로 읽을 수 있습니다.