🔥 🔥 자바스크립트 비동기 뿌시기 2편 - 프로미스 & async / await

2025. 4. 2. 18:41·뿌시기 시리즈

나에게 넘나 어려운 비동기.. 왜 굳이 굳이 promise를 사용해서 비동기로 작업해야 하는지에 대해 궁금증이 항상 가득했었다. 물음표 살인마인 나에게는 넘나 고통스러운 것. 그러던 와중에 강같은 글을 발견해서, 그 글을 보고 내 방식대로 다시 정리해보았다.

(출처: https://springfall.cc/article/2022-11/easy-promise-async-await)

 

Promise 란?

: 프로미스 객체는 생산자와 소비자를 연결해준다.

 

예시)

⇒ 커피 주문이 오면 프로미스 객체에 넣어준다.

⇒ 프로미스 객체에서 커피를 만들어서 보관한다.

⇒ 소비자에서 .then()을 하는데 이게 바로 호출되는 게 아니라, 프로미스가 소비자에게 커피가 다 만들어졌다는 것을 알려줘야 .then에 전달된 콜백함수가 실행된다.

 

.then으로 체이닝해서 순서대로 비동기 작업을 실행할거면, 동기적으로 작업하면 되지 왜 굳~~~이 비동기로 작업해야 하는 이유

⇒ 동기적으로 작업하면 1개의 작업을 하는 동안 나머지 작업을 못한다. 하지만 비동기적으로 작업을 하면 외부로부터 데이터를 읽어오는, 즉 오랜 시간이 걸리는 작업을 하는 동안 비동기로 체이닝한 작업을 실행할 수 있기 때문에 굳~~~이 비동기 작업으로 하는 것.

 

Promise 기본 사용법

  • promise = 비동기 작업의 단위
  • Promise로 관리할 비동기 작업을 만드는 정석적인 방법은 new Promise() 이다.
    • new Promise() 를 통해 새로운 Promise 객체를 생성했고, 이 Promise 생성자는 특별한 콜백 함수(executor)를 인자로 받는다.
    • executor는 인수로 resolve, reject 를 전달받는다.
    • resolve : executor 내에서 호출할 수 있는 또 다른 함수. resolve 를 호출하게 된다면 “이 비동기 작업이 성공했어!”를 의미함.
    • reject : executor 내에서 호출할 수 있는 또 다른 함수. reject를 호출하게 된다면 “이 비동기 작업이 실패했서,,, ㅜㅜ”를 의미함.
    • ⇒ 쨌든 여기까지 promise1이라는 Promise 객체를 얻었음.
  • const promise1 = new Promise((resolve, reject) => { // 비동기로 처리할 작업 });

 

Promise의 특징

  • new Promise() 를 하는 순간, 이 안에 작성한 비동기 작업은 바로 시작된다. 즉, 그냥 기다리지 않고 바로 생성자 안의 코드를 실행함.
  • 비동기 작업이 끝난 후 작업이 성공하거나 실패했을 때의 뒷처리를 해줘야 한다. Promise가 끝나고 난 다음의 동작을 우리가 설정해줄 수 있음. ⇒ then , catch
    • then : 해당 Promise가 성공했을 때(resolve)의 동작을 지정한다. 인자로 함수를 받음.
    • catch : 해당 Promise가 실패했을 때(reject)의 동작을 지정한다. 인자로 함수를 받음.
      ⇒ 위 메소드들은 체인 형태로 활용할 수 있다. (=연속적으로 호출할 수 있음)
```jsx
// 1. promise1 내에서 resolve만 호출했을 때
const promise1 = new Promise((resolve, reject) => {
  resolve();
});

// promise를 호출하고, 비동기 작업이 끝났을 때 성공, 실패 여부에 따른 뒷처리
promise1
  .then(() => {
    console.log("then!");
  })
  .catch(() => {
    console.log("catch");
  });
  // 실행결과 => then!


  // 2. promise1 내에서 reject만 호출했을 때
const promise1 = new Promise((resolve, reject) => {
  reject();
});

promise1
  .then(() => {
    console.log("then!");
  })
  .catch(() => {
    console.log("catch!");
  });
  // 실행결과 => catch!
```

- then의 인자로 then을 출력해주는 콜백함수를 전달해주었고, 연속적으로 catch에도 함수를 전달해주었다.
- 예제 1) promise1에서 resolve()가 바로 호출됐기 때문에 성공으로 간주 ⇒ `then` 에 있는 동작만 실행됨.
- 예제 2) promise1에서 reject()가 바로 호출됐기 때문에 성공으로 간주 ⇒ `catch`에 있는 동작만 실행됨.

 

Promise 재사용하기

:new Promise() 를 하는 순간 비동기 작업이 시작된다. 그러면 비슷한 비동기 작업을 수행할 때마다 매번 new Promise() 를 해줘야 할까?

  function startAsync(age) {
    return new Promise((resolve, reject) => {
      if (age > 20) resolve();
      else reject();
    });
  }

  const promise1 = startAsync(25);

  promise1
    .then(() => {
      console.log("1 then");
    })
    .catch(() => {
      console.log("1 catch");
    });

  const promise2 = startAsync(15);

  promise2
    .then(() => {
      console.log("2 then");
    })
    .catch(() => {
      console.log("2 catch");
    });

  // 실행 결과
  1 then
  2 catch
  • startAsync 함수를 호출하는 순간, new Promise() 가 실행되어서 바로 비동기 작업이 시작된다.
  • 비동기 작업이 성공할지 실패할지는 장담할 수 없음 ⇒ then, catch 로 후속 동작을 지정함.
  • promise1 은 성공함 ⇒ then 으로 지정한 동작만 수행함.
  • promise2 은 실패함 ⇒ catch 로 지정한 동작만 수행함.
    ⇒ 그럴 필요없이 함수 내에서 return new Promise() 해주면 된다.

 

promise에서 실행한 작업 결과를 전달하기

  • resolve, reject 함수에 인자를 전달하면 then, catch 에서 사용할 수 있음
function startAsync(age) {
  return new Promise((resolve, reject) => {
    if (age > 20) resolve(`${age} success`);
    else reject(new Error(`${age} is not over 20`));
  });
}

const promise1 = startAsync(25);

promise1
  .then((value) => {
    // value => resolve 함수에 전달된 `${age} success`
    console.log(value);
  })
  .catch((err) => {
    console.log(err.message);
  });

const promise2 = startAsync(15);

promise2
  .then((value) => {
    console.log(value);
  })
  .catch((err) => {
    console.log(err.message);
  });

// 실행 결과
25 success
15 is not over 20

 

executor 함수 작성할 때 고려사항

  • executor 내부에서 에러가 throw 된다면 해당 에러로 reject가 수행된다.
// reject됨 => catch 작동함.
const throwError = new Promise((resolve, reject) => {
  throw Error("error");
});

throwError
  .then(() => console.log("throwError success"))
  .catch(() => console.log("throwError catched"));
  
// 실행 결과
throwError catched

 

  • executor의 리턴값은 무시된다.
const ret = new Promise((resovle, reject) => {
  return "returned";
});
ret
  .then(() => {
    console.log("ret success");
  })
  .catch(() => {
    console.log("ret catched");
  });
  
  // 실행 결과 없음 => return값이 아무런 영향을 끼치지 않음.

 

  • 첫번째 reject 혹은 resolve 만 유효하다. (두번째부터는 무시됨. 이미 해당 함수가 호출됐다면 throw 또한 무시된다.)
    ⇒ 어차피 첫번째 resolve, reject만 영향을 주기 때문에 해당 함수가 호출되면 바로 return해서 비동기 작업을 빠져나간다.
// 1. resolve만 실행되는 경우
const several1 = new Promise((resolve, reject) => {
  resolve();
  reject();
});
several1
  .then(() => console.log("several1 success"))
  .catch(() => console.log("several1 catched"));

// 실행 결과 : several success

// 2. reject만 실행되는 경우
const several2 = new Promise((resolve, reject) => {
  reject();
  resolve();
});
several2
  .then(() => console.log("several2 success"))
  .catch(() => console.log("several2 catched"));
// 실행결과 : several2 catched

// 3. resolve만 실행되는 경우
const several3 = new Promise((resolve, reject) => {
  resolve();
  throw new Error("error");
});
several3
  .then(() => console.log("several3 success"))
  .catch(() => console.log("several3 catched"));
// 실행결과 : several3 success

 

setTimeout을 Promise 버전으로 만들기

setTimeout(callback, delay);
  • setTimeout : 인자로 주어진 콜백함수를 주어진 delay초 뒤에 실행한다.⇒ 실패했을 때는 일단 없다고 가정함.
    ⇒ 주어진 delay를 기다리는 것을 완료한다면 성공(fulfilled), 성공했다면 callback을 실행함.
function setTimeoutPromise(delay) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve();
    }, delay);
  });
}
console.log("promise가 시작됩니다.");

const promise = setTimeoutPromise(1000);

promise.then(() => {
  console.log("끝났습니다!");
});

// Refactoring
function setTimeoutPromise(delay) {
  return new Promise(
    (resolve) => setTimeout(resolve, delay) // reject 생략 및 화살표 함수 축약 가능
  );
}
console.log("promise가 시작됩니다.");

const promise = setTimeoutPromise(1000);

promise.then(() => {
  console.log("끝났습니다!");
});

setTimeoutPromise 함수를 호출했을 때 벌어지는 일

  1. setTimeout 이 즉시 실행됨.
  2. delay 밀리 초 뒤에 resolve() 가 실행됨.
  3. 해당 promise의 then 으로 연결된 후속 작업이 실행된다.
  4. 🍓 기다렸다는 사실만 중요하므로 resolve에 별다른 값을 전달하지 않았음.
저작자표시 (새창열림)
'뿌시기 시리즈' 카테고리의 다른 글
  • 🔥 자바스크립트 비동기 뿌시기 1편 - 콜백 함수
  • 🔥 실행 컨텍스트 뿌시기
  • 🔥 자바스크립트 배열 뿌시기 (feat. 배열 메서드, 배열 고차함수)
  • 🔥 깃허브 협업, Git flow 뿌시기!
킵고양
킵고양
궁금한 게 넘모 많아요 그래도 keep goyang
  • 킵고양
    개발할고양
    킵고양
  • 전체
    오늘
    어제
    • 분류 전체보기
      • 프로젝트
      • 갱발자 레벨업
        • 갱스타그램
        • 코테
      • 뿌시기 시리즈
      • Things I Learned
  • 인기 글

  • 태그

    무한스크롤
    javascript
    Promise
    개발자
    MUI
    리액트
    프로젝트회고
    프로토타입
    코딩테스트
    배열
    Zustand
    프로그래머스
    props
    실행컨텍스트
    gitflow
    useCallback
    접근성
    프론트엔드
    비동기
    자바스크립트
    useRef
    V8엔진
    GitHub
    react
  • hELLO· Designed By정상우.v4.10.3
킵고양
🔥 🔥 자바스크립트 비동기 뿌시기 2편 - 프로미스 & async / await
상단으로

티스토리툴바