Post

[Next.js] 비동기로 파일을 여러 개 업로드 할 때 콜백 처리하기 (ft. axios)

내용

비동기로 파일을 여러개 업로드 할 때, 마지막 파일의 업로드가 완료되면 콜백을 실행해야하는 경우가 있습니다. 이럴 때 파일 업로드 상황을 파악하기 위해 쓸데없이 코드가 길어질 수 있는데, axios에서는 이런 상황에서 쓸 수 있게 axios.all()이라는 메서드를 제공하고 있습니다. 이번 포스트에는 간단한 예시와 함께 axios.all() 사용법과 파일 업로드 progress를 구현하는 법을 정리했습니다.

 

설치 패키지 및 버전

1
2
3
4
5
"dependencies": {
    "next": "13.4.1",
    "axios": "^1.4.0",
    "typescript": "5.0.4",
}

 

레이아웃 구성

먼저 간단하게 기능을 구현하기 위해 <input type="file"/>을 넣고, multiple 속성을 지정해 줬습니다. input에는 multiple 속성을 넣어줘야 파일을 여러 개 업로드할 수 있습니다. 그 아래에는 파일 업로드 진행 상황을 표시하기 위한 빈 div를 만들었습니다. 실제 코드는 더 길겠지만, 가독성을 위해 스타일 관련된 부분은 과감히 생략.

1
2
<input type="file" onChange={handleChange} multiple id="my-file"/>
<div style=`width: ${progress}%`></div>

 

axios.all()

axios.all()은 axios에서 여러 개의 요청을 동시에 수행(멀티 리퀘스트)할 경우 사용하는 메서드입니다. 멀티 리퀘스트를 보내야 하는 상황이 바람직하지 않다고는 하지만,, 어느 정도는 괜찮지 않을까,, 그래도 언제나 클라이언트단에서 API 요청은 최소화하는 게 좋습니다.

axios.all() 사용 예시

1
2
3
4
5
6
7
8
9
10
11
12
function getUserAccount() {
  return axios.get('/user/12345');
}

function getUserPermissions() {
  return axios.get('/user/12345/permissions');
}

axios.all([getUserAccount(), getUserPermissions()])
.then(axios.spread(function (acct, perms) {
    // Both requests are now complete
}));

 

함수 설계

  1. progress를 담을 state를 지정한다.
  2. input에 파일이 업로드되면 파일의 개수만큼 axios.post()를 실행한다.
  3. axios의 onUploadProgress에서 전달받은 값을 progress에 저장한다.
  4. axios.post() 실행이 모두 완료되면 axios.all()로 콜백을 실행한다.

전체 코드 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const [progress, setProgress] = useState<number>(0);

const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
	if (e.target.files) {
        let allPromise = [];
        let percentage = 0;
        
        // 전체 파일 크기 계산
        let totalSize = 0;
        for (let i = 0; i < e.target.files.length; i++) {
        	totalSize += Number(e.target.files[i].file.size);
        }
    
    	// 이미지 개수만큼 axios.post() 실행
    	for (let i = 0; i < e.target.files.length; i++) {
            let promise = await axios.post("API 경로", data, {
                // progress return 받기
                onUploadProgress: (progressEvent: AxiosProgressEvent) => {
                    if (progressEvent && progressEvent.loaded && progressEvent.total) {
                        // 기존 progress + (파일크기/전체파일크기)를 progress에 저장
                        percentage = percentage + Number(Math.round(e.target.files[i].file.size) / totalSize * 100);
                        setProgress(percentage);
                    }
                },
                headers: {
                    "Context-Type": "multipart/form-data"
                }
            })
            .then(() => console.log(`이미지 ${i} 업로드 성공`))
            .catch(() => console.log(`이미지 ${i} 업로드 실패`));
            
            // allPromise에 promise 저장
            allPromise.push(promise);
        }
        
        // axios.post() 실행이 모두 완료되면 axios.all() 실행
        await axios.all(allPromise)
        .then(() => console.log("이미지 모두 업로드 성공"))
        .catch(() => console.log("이미지 모두 업로드 실패"));
    }
}

여러 개의 파일을 업로드하는 경우 시간이 오래 걸리기 때문에, 진행 상황을 화면에 표시하는 게 좋을 거 같아서 파일을 따로따로 업로드한 후, progress를 return 받는 식으로 구현했습니다. progress는 기존에 지정된 progress + (파일 크기/전체 파일 크기)로 나온 값을 지정해 줬고, progress가 늘어날 때마다 아까 레이아웃에 넣은 div의 넓이가 증가하는 방식으로 클라이언트에서 진행 상황을 볼 수 있게 했습니다.

 

참고

파일의 개수만큼 API 요청을 보내는 게 비효율적인 것 같지만, 구글링해도 다른 방법이 딱히 나오지 않아서,, 일단은 이런 방식도 있구나 정도로 생각해도 좋을 거 같습니다.

모든 이미지가 업로드되면 미리보기 화면을 띄우는 기능 모든 이미지가 업로드되면 미리보기 화면을 띄우는 기능

 

레퍼런스

This post is licensed under CC BY 4.0 by the author.