[JS] Promise를 사용해 함수의 타임아웃 기능을 구현하는 방법

jiny·2025년 2월 9일

기술 면접

목록 보기
58/78

🗣️ Promise를 사용해 함수의 타임아웃 기능을 구현하려면 어떤 메서드를 사용해야 할까요?

  • 의도: Promise에 대한 이해도를 확인하는 질문

  • 팁: Promise.race()를 언급하는 게 정답이다.

  • 나의 답안

    Promise를 사용해 타임아웃 기능을 구현할 때는 보통 Promise.race() 메서드를 사용합니다.
    Promise.race()는 여러 개의 Promise 중에서 가장 먼저 끝나는 것 하나만 결과로 반환하기 때문에, 비동기 작업과 타이머를 동시에 실행시켜서 둘 중 먼저 끝나는 걸 기준으로 처리할 수 있습니다.

    예를 들어, 어떤 요청이 일정 시간 안에 완료되지 않으면 타이머 쪽이 먼저 완료돼서 에러를 발생시키도록 만들면, 결과적으로 타임아웃 기능을 구현할 수 있습니다.
    이렇게 하면 네트워크 요청이나 오래 걸리는 비동기 작업에서 예측 가능한 제어 흐름을 만들 수 있고, 사용자 경험도 훨씬 안정적으로 관리할 수 있습니다.

  • 주어진 답안 (모범 답안)

    Promise.race()를 사용하면 됩니다.
    이 함수의 기능은, 인자에 비동기 함수를 넣어주게 되면 넣어준 함수들 중 가장 빠르게 resolve되는 함수의 결과만을 가져오게 됩니다.

    그러면 여기에 내가 사용할 비동기 함수와, setTimeout을 함께 넣어주게 된다면 비동기 함수와 setTimeout 둘 중 먼저 끝나는 쪽을 반환해주게 됩니다.
    만약 setTimeout을 3초로 정해둔다면 3초 안에 비동기 함수가 resolve되지 않을 시 타임아웃 판단을 내리도록 구현할 수도 있습니다.


📝 개념 정리

🌟 Promise.race()의 개념

  • Promise.race()여러 개의 프로미스 중에서 가장 먼저 완료되는(fulfilled 또는 rejected) 프로미스를 반환하는 메서드이다.
  • 즉, 여러 개의 프로미스를 전달하면, 그 중에서 가장 빠르게 settled(상태가 확정된)된 프로미스의 결과를 반환한다.

🌟 Promise.race()의 동작 방식

Promise.race(iterable);
  • iterable: 배열 또는 이터러블 객체로, 여러 개의 Promise를 포함할 수 있다.
  • 반환값: 가장 먼저 이행(fulfilled) 또는 거부(rejected)Promise의 상태와 결과를 그대로 반환한다.

즉, 가장 먼저 완료되는 Promise의 결과를 가져오므로, 나머지 Promise들은 그대로 남아 있지만, race()결과에 영향을 미치지 않는다.


🌟 Promise.race() 예제

  1. 가장 빠른 Promise가 결과를 반환하는 예제

    const p1 = new Promise((resolve) => setTimeout(resolve, 500, "첫 번째 완료"));
    const p2 = new Promise((resolve) => setTimeout(resolve, 300, "두 번째 완료"));
    const p3 = new Promise((resolve) => setTimeout(resolve, 1000, "세 번째 완료"));
    
    Promise.race([p1, p2, p3]).then(console.log);
    // 출력: "두 번째 완료"
    • p2가 300ms로 가장 먼저 완료되므로, Promise.race()는 "두 번째 완료"를 반환한다.
  1. Promise가 reject 되는 경우
    가장 먼저 reject된 Promise가 있으면, .catch()로 처리할 수 있다.

    const p1 = new Promise((resolve) => setTimeout(resolve, 500, "성공"));
    const p2 = new Promise((_, reject) => setTimeout(reject, 300, "실패"));
    
    Promise.race([p1, p2]).then(console.log).catch(console.error);
    // 출력: "실패"
    • p2가 300ms 후 reject되므로, .catch()로 에러 처리된다.
  1. Promise.race()에 즉시 완료되는 Promise가 포함된 경우

    const p1 = Promise.resolve("즉시 완료");
    const p2 = new Promise((resolve) => setTimeout(resolve, 1000, "1초 후 완료"));
    
    Promise.race([p1, p2]).then(console.log);
    // 출력: "즉시 완료"
    • 즉시 resolve된 Promise가 있다면, 그것이 가장 먼저 완료된 것으로 간주된다.

🌟 Promise.race()의 활용 사례

  1. 네트워크 요청의 타임아웃 설정
    네트워크 요청이 너무 오래 걸릴 경우, 일정 시간이 지나면 자동으로 실패하게 설정할 수 있다.

    function fetchWithTimeout(url, timeout) {
      const fetchPromise = fetch(url).then((res) => res.json());
      
      const timeoutPromise = new Promise(
        (_, reject) => setTimeout(() => reject("요청 시간이 초과되었습니다!"), timeout)
      );
      
      return Promise.race([fetchPromise, timeoutPromise]);
    }
    
    fetchWithTimeout("https://jsonplaceholder.typicode.com/todos/1", 2000)
      .then(console.log)
      .catch(console.error);
    • 네트워크 요청(fetch)이 2초 안에 완료되지 않으면 "요청 시간이 초과되었습니다!"라는 에러가 발생한다.
  1. 빠른 응답 서버 선택 (백업 서버)
    여러 개의 서버 중 가장 빠른 응답을 반환하는 서버를 선택할 때 활용할 수 있다.

    const server1 = fetch("https://api.server1.com/data");
    const server2 = fetch("https://api.server2.com/data");
    
    Promise.race([server1, server2])
      .then((response) => response.json())
      .then(console.log)
      .catch(console.error);
    • server1server2 중 가장 빠른 응답을 제공하는 서버의 데이터를 사용한다.

🌟 Promise.race()의 주의할 점

  1. 나머지 Promise들은 계속 실행된다.
    • race()에서 반환되지 않은 Promise는 취소되지 않고 계속 실행된다.
    • 필요할 경우, 별도로 취소하는 로직을 추가해야 한다.
  1. 예외 처리를 신경 써야 한다.
    • Promise.race()에서 가장 먼저 reject된 Promise가 있다면, 이를 .catch()로 잡아야 한다.

🌟 Promise.race() vs. Promise.all() vs. Promise.any()

메서드설명성공 조건실패 조건
Promise.race()가장 먼저 완료된 Promise를 반환첫 번째 완료된 Promise첫 번째 reject된 Promise
Promise.all()모든 Promise가 완료되면 결과 배열 반환모든 Promise가 성공해야 함하나라도 reject되면 전체 실패
Promise.any()하나라도 resolve되면 그 값을 반환첫 번째 resolve된 Promise모든 Promise가 reject될 때 실패

🌟 결론

  • Promise.race()는 가장 빨리 완료된 Promise의 결과를 반환하므로, 타임아웃 구현, 백업 서버 요청 등 빠른 응답이 중요한 경우에 유용하다.
  • 하지만 race()를 사용하면 나머지 Promise가 계속 실행되므로, 필요할 경우 추가적인 취소 로직이 필요하다.
  • Promise.all(), Promise.any()와 비교하여 언제 사용할지 결정하는 것이 중요하다.

0개의 댓글