Promise vs Promise.all :: 이미지 여러개를 한번에 변환하기

이주희·2022년 5월 1일
0

JavaScript

목록 보기
28/49

이미지 성능 최적화
[기존 방식]
[미리보기]
[Promise.all로 한번에 변환하기]
[LazyLoad, PreLoad, 이미지 관련 라이브러리]


같은 요청을 여러번 해야 할 때 아래처럼 하나하나 적어도 되지만

for문을 이용하면 더 간결해질 것이다.

하지만 이 방법은 코드는 줄지만 성능상의 이점은 없다.
promise.all을 이용해서 성능을 향상시켜보자!

promise

시간이 걸리는 작업을 할 때 promise를 이용한다.

    const result = await new Promise((resolve, reject) => { 
      setTimeout(() => {
        resolve("철수");
      }, 3000);
    });
/* 위 아래 같다 */
    new Promise((resolve, reject) => { // resolve가 실행될때까지 기다린다.
      // Promise는 시간이 걸리는 작업에 쓴다.
      setTimeout(() => {
        resolve("철수");
      }, 3000);
    }).then(res=>res) // resolve로 넘겨준 "철수"가 res에 담긴다.
  };

promise.all

  • 요청이 한번에 보내진다.(promise는 한번에 하나씩 요청이 간다.)
  • promise 요청을 한번에 배열에 담아서 보내고, 결과도 배열로 한번에 받는다.

    const result = await Promise.all(
      ["https://dog1.jpg", "https://dog2.jpg", "https://dog3.jpg"].map(
        (el) =>
          new Promise((resolve, reject) => {
            setTimeout(() => {
              resolve(el);
            }, 3000);
          })
      )
    );

promise와 promise.all 소요 시간 비교

promise로 요청하면 각각 하나씩 실행되어서 총 6초가 소요된다.

const onClickPromise = async () => {
    console.time("Promise 요청");

    const result1 = await new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve("https://dog1.jpg");
      }, 3000);
    });
    console.log(result1);

    const result2 = await new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve("https://dog2.jpg");
      }, 1000);
    });
    console.log(result2);

    const result3 = await new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve("https://dog3.jpg");
      }, 2000);
    });
    console.log(result3);
    console.timeEnd("Promise 요청"); // 6초
  };

promise.all로 요청하면 총 3초가 걸린다.

  const onClickPromiseAll = async () => {
    console.time("PromiseAll 요청");
    
    const result = await Promise.all([
      new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("https://dog1.jpg");
        }, 3000);
      }),
      new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("https://dog2.jpg");
        }, 1000);
      }),
      new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("https://dog3.jpg");
        }, 2000);
      }),
    ]);
    
    console.timeEnd("PromiseAll 요청"); // 3초
  }

Promise.all을 이용해서 uploadFile 한번에 요청하기!

    const results = await Promise.all(
      files.map((el) => el && uploadFile({ variables: { file: el } }))
    );

전체 코드

import { gql, useMutation } from "@apollo/client";
import { ChangeEvent, useState } from "react";

const CREATE_BOARD = gql`
  mutation createBoard($createBoardInput: CreateBoardInput!) {
    createBoard(createBoardInput: $createBoardInput) {
      _id
    }
  }
`;
const UPLOAD_FILE = gql`
  mutation uploadFile($file: Upload!) {
    uploadFile(file: $file) {
      url
    }
  }
`;
export default function ImageUploadPreviewPage() {
  const [files, setFiles] = useState<(File | undefined)[]>([
    // 타입: File 또는 undefined가 들어있는 배열 (파일이 항상 3개일 필요는 없으니까~)
    undefined,
    undefined,
    undefined,
  ]);
  const [imageUrls, setImageUrls] = useState(["", "", ""]);
  const [uploadFile] = useMutation(UPLOAD_FILE);
  const [createBoard] = useMutation(CREATE_BOARD);

  const onChangeFile =
    (number: number) => (e: ChangeEvent<HTMLInputElement>) => {
      const file = e.target.files?.[0]; // 배열도 옵셔널체이닝을 쓸 수 있다.
      if (!file) {
        alert("파일이 없습니다.");
        return;
      }
      const fileReader = new FileReader(); // JS의 내장기능이다.
      fileReader.readAsDataURL(file); // file은 Blob(이진 형태의 큰 데이터)
      fileReader.onload = (data) => {
        // file을 다 읽으면 읽어진 결과물이 data로 들어오고 이 함수가 실행된다.
        if (typeof data.target?.result === "string") {
          const tempUrls = [...imageUrls]; // 여기에 값을 바꿔서 다시 setImageUrls 해줄건데, 그냥 넣으면 얕은복사때문에 값이 안바뀌니까 tempUrls에 스프레드시켜서 복사한다.
          tempUrls[number] = data.target?.result; // 변경된 number(배열의 인덱스로 사용)만 변경한다.
          setImageUrls(tempUrls);

          const tempFiles = [...files]; // file도 위의 imageUrls와 동일하게 복사해준다.
          tempFiles[number] = file;
          setFiles(tempFiles); // uploadFile API에 보내기 위한 *File*
        }
      };
    };

  const onClickSubmit = async () => {
    // undefined는 무시된다.
    const results = await Promise.all(
      files.map((el) => el && uploadFile({ variables: { file: el } }))
    );

    const resultUrls = results.map((el) => {
      // el.data가 없으면 파일을 업로드 하지 않은 것이기 때문에 빈 문자열로 받는다.
      return el?.data ? el?.data.uploadFile.url : "";
    });

    const result2 = await createBoard({
      variables: {
        createBoardInput: {
          writer: "juhee",
          title: "Ttttttttitle",
          contents: "Connnnnntents",
          password: "1234",
          images: resultUrls,
        },
      },
    });
    console.log(result2.data.createBoard._id);
  };
  return (
    <div>
      <input type="file" onChange={onChangeFile(0)} />
      <input type="file" onChange={onChangeFile(1)} />
      <input type="file" onChange={onChangeFile(2)} />
      <img src={imageUrls[0]} />
      <img src={imageUrls[1]} />
      <img src={imageUrls[2]} />
      <button onClick={onClickSubmit}>게시글 등록하기</button>
    </div>
  );
}
profile
🍓e-juhee.tistory.com 👈🏻 이사중

0개의 댓글