[ ᴊᴀᴠᴀꜱᴄʀɪᴘᴛ ] 비동기 프로그래밍과 Promise, Async/Await

NewHa·2023년 11월 4일
post-thumbnail

비동기 프로그래밍

동기와 비동기

동기란 결과를 기다렸다가 다음 일을 수행하는 것이고, 비동기란 결과를 기다리지 않고 바로 다음 일을 수행하는 것을 의미합니다. 택배 배송을 예로 들면, 동기적으로 배송하는 것은 첫 번째 집에 도착해 벨을 누르고, 기다렸다가 사람이 나오면 물건을 전해주고 다음 집으로 향하는 방식입니다. 반면, 비동기적으로 배송하는 것은 첫 번째 집에 도착해서 택배를 문 앞에 두고 바로 다음 집으로 가는 방식입니다.

비동기 방식은 빠르고 효율적이지만 몇 가지 문제가 있습니다. 예를 들어, 누군가 택배를 훔쳐가는 경우, 택배 기사는 물건이 주인에게 전달되었는지 확인할 방법이 없어 문제가 생길 수 있습니다. 이를 방지하기 위해 택배 기사는 문 앞에 택배를 두고 사진을 찍어 물건 주인에게 문자로 알리는 동작을 더합니다. 이처럼 비동기적으로 처리하는 경우, 추가적인 확인 절차가 필요합니다.

동기적인 처리 방식은 단순합니다. 결과를 확인하고 다음 작업을 수행하므로 안전하고 예측하기 쉽습니다. 하지만 결과를 기다리는 시간이 발생하므로 비효율적일 수 있습니다. 반면, 비동기적인 방식은 기다림 없이 작업을 이어나가기 때문에 빠르고 효율적이지만, 결과 확인을 대체하는 복잡한 처리를 요구할 수 있습니다.

하지만 빠르고 효율적으로 보인다고 모든 작업을 비동기로 처리하는 것은 적절하지 않을 수 있습니다. 예를 들어, 택배로 목걸이를 만드는 재료를 받고 이를 이용해 목걸이를 제작한 뒤 구매자에게 보내는 작업을 생각해봅시다. 재료를 받지 않거나 잘못된 재료를 받고 목걸이를 제작하려 하면 문제가 생길 수 있습니다. 따라서 작업의 특성에 따라 동기와 비동기 방식을 적절히 선택해야 합니다. 이처럼 결과가 다음 행동에 지장을 준다면 동기적으로 처리해야 합니다.

동기프로그래밍과 비동기프로그래밍

동기 프로그래밍

동기 프로그래밍은 특정 코드의 실행이 완료될 때까지 기다린 뒤 다음 코드를 실행하는 방식입니다. 코드가 순차적으로 실행되므로 실행 순서를 예측하기 쉽고 작성하기도 간단합니다. 하지만 매 작업이 완료될 때까지 기다려야 하므로 blocking 문제가 발생할 수 있습니다.

📍

Blocking

한 작업이 끝날 때까지 다음 작업이 대기해야 하는 상황을 의미합니다. 이로 인해 프로그램 전체 실행이 멈추거나 처리 속도가 느려질 수 있습니다. 주로 동기적 작업에서 발생하며, 단일 스레드 기반 언어인 자바스크립트에서 특히 주의가 필요합니다.
네트워크 요청, 파일 입출력, 데이터베이스 접근 등 시간이 오래 걸리는 작업을 동기적으로 처리하면 사용자는 프로그램이 멈추고, 버튼클릭, 스크롤, 애니메이션 등이 실행되지 않습니다.
자바스크립트에서 이를 해결하기 위해 비동기 프로그래밍을 사용합니다. 대표적으로 콜백 함수, Promise, Async/Await이 있습니다.
Blocking 문제는 이벤트 루프의 흐름을 막아 다른 작업이 진행되지 못하게 합니다. 비동기 프로그래밍은 이벤트 루프를 활용해 호출 스택을 비워 작업을 효율적으로 처리하므로 Blocking 문제를 예방할 수 있습니다.

const syncFunction () {
  console.log('1번 작업');
  console.log('2번 작업');
}

syncFunction(); // '1번 작업' '2번 작업'

위 코드는 자바스크립트가 기본적으로 동기적으로 작동함을 보여줍니다. 실행 결과는 순차적으로 출력됩니다.

비동기 프로그래밍

비동기 프로그래밍은 특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 실행하는 방식입니다.

📍

자바스크립트에서 경험할 수 있는 비동기 작업

자바스크립트에서는 타이머와 관련된 API에서 비동기를 경험할 수 있습니다.

  • setTimeout(callback, millisecond) : 일정 시간 후에 콜백 함수를 실행합니다. (return 임의의 타이머 ID)
  • clearTimeout(timerId) : setTimeout 타이머를 종료합니다. (return X)
  • clearInterval(callback, millisecond) : 일정 시간 간격으로 콜백 함수를 반복 실행합니다. (return 임의의 타이머 ID)
  • clearIntervla(timerId) : setInterval 타이머를 종료합니다. (return X)
const asyncFunction () {
  console.log('1번 작업');
  setTimeout(() => console.log('비동기 작업'), 2000);
  console.log('2번 작업');
}

asyncFunction(); // '1번 작업' '2번 작업' '비동기 작업'

위 코드는 차례로 실행하다 비동기 작업(setTimeout)을 만나면 기다리지 않고 건너 뛰어 다음 작업을 실행합니다. 그리고 2000ms 후에 비동기 작업이 완료되면 그 때 처리됩니다.
이처럼 비동기 방식은 blocking 문제를 해결하고 빠르게 작업을 처리할 수 있게 합니다.

자바스크립트 비동기 작업

자바스크립트는 단일 스레드 기반 언어로, 한 번에 하나의 작업만 처리할 수 있습니다. 동시에 여러 작업을 처리해야 하는 경우 비동기 작업을 통해 효율적으로 실행 순서를 관리합니다. 비동기 작업은 동기적인 코드의 흐름을 차단하지 않으면서 시간이 오래 걸리는 작업(네트워크 요청(fetch API)나 이벤트 리스너, 파일 읽기, 타이머 등)을 처리할 수 있게 합니다.

비동기 작업은 이벤트 루프(Event Loop) 를 통해 관리됩니다.
우선, 자바스크립트는 호출 스택을 사용해 함수 호출을 관리합니다. 함수가 호출되면 스택에 추가되고, 실행이 끝나면 스택에서 제거됩니다.

function func1() {
  console.log('1번 함수 실행');
}

funcstion func2() {
  func1();
  console.log('2번 함수 실행');
}

func2();

위의 코드를 실행하면 호출스택은 우선 func2() 호출부분을 추가하고, func2 내부에서 func1() 호출 부분을 추가합니다. func1을 실행하고 '1번 함수 실행'을 출력하고 나면 스택에서 제거되고 func2로 다시 돌아가 나머지 '2번 함수 실행'을 출력하고 나서 스택에서 제거됩니다.

이 때, setTimeout, fetch, DOM이벤트 등 비동기 작업은 호출 스택에서 실행되지 않고 브라우저의 Web API에 의해 처리됩니다. 웹 API는 비동기 작업을 실행하고 콜백 함수를 태스크 큐로 보냅니다.

console.log('시작');

setTimeout(() => {
  conosole.log('비동기 작업')
}, 1000);

console.log('끝');

위의 코드를 실행하면 console.log()가 호출 스택에서 실행되어 '시작'을 출력합니다. setTimeout을 만나서 해당 부분을 브라우저의 웹 API에 넘겨서 타이머를 설정하고 넘어갑니다. console.log()가 실행되며 '끝'을 출력합니다. 1000ms 타이머가 끝나면 setTimeout의 콜백인 () => console.log(..) 가 태스크 큐에 들어가고, 호출 스택이 비어있으면, 이벤트 루프가 태스크 큐에서 콜백을 실행하고 '비동기 작업'을 출력합니다.

이 때, Promise, MutationObserver 같은 작업은 마이크로태스크 큐에서 관리합니다. 마이크로태스크 큐는 태스크 큐보다 우선순위가 높습니다.

setTimeout(() => {
  console.log("setTimeout");
}, 0);

Promise.resolve().then(() => {
  console.log("Promise");
});

위의 코드를 실행하면 둘 모두 비동기 작업이므로 즉시 실행하지 않고 브라우저가 처리합니다. setTimeout은 태스크 큐에 등록하고 넘어가고, Promise.resolve()는 호출 즉시 새 Promise객체를 반환하면서 이행 상태로 변경되고, .then()의 콜백함수가 마이크로태스크 큐에 등록되고 넘어갑니다(이행 상태이더라도 즉시 실행되지 않습니다). 호출 스택이 비어있으면 이벤트 루프가 실행될 준비가 됩니다. 이벤트 루프는 항상 마이크로태스크 큐를 먼저 확인하고 실행합니다. 마이크로태스크 큐에 있는 .then()의 콜백 함수를 처리해 Promise를 출력하고, 태스크 큐에서 작업을 가져와 이를 처리해 setTimeout을 출력합니다. 즉, 태스크 큐에서 처리하는 setTimeout은 호출 스택과 마이크로태스크 큐가 모두 처리된 후에 실행됩니다.

Callback function (콜백 함수)

비동기 작업을 작성하다 보면 콜백 함수를 만나게 됩니다. 콜백 함수는 다른 함수의 인자로 전달되고, 해당 함수가 처리된 후 호출됩니다.

const syncFunction (callback) {
  console.log('1번 작업');
  console.log('2번 작업');
  callback();
}

syncFunction(() => console.log('콜백 함수 실행')); // '1번 작업' '2번 작업' '콜백 함수 실행'

위 코드에서 syncFunction은 콜백 함수(() => console.log('...'))를 받아 차례로 실행이 끝난 뒤 해당 콜백을 호출합니다. 그래서 함수 내에서 다시(back) 호출(call)된다고 해서 call+back 함수입니다. 여기서 중요한 점은 콜백 함수가 항상 비동기적으로 실행되는 것은 아니다는 것입니다. 콜백함수는 내부적으로 동기적인지 비동기적인지에 따라 작동합니다.

const asyncFunction (callback) {
  console.log('1번 작업');
  setTimeout(callback(), 2000);
  console.log('2번 작업');
}

syncFunction(() => console.log('콜백 함수 실행')); // '1번 작업' '2번 작업' '콜백 함수 실행'

위 코드는 setTimeout과 같은 비동기 작업 내부에서 콜백 함수를 사용한 예입니다. 콜백 함수 자체는 동기적이므로 기존의 비동기 함수의 예시와 똑같이 실행됩니다. 다만, 비동기 작업 후에 콜백 함수가 호출되므로 이를 활용해 비동기 작업 순서를 동기 작업 처럼 제어할 수 있습니다.

callback hell (콜백 헬)

비동기 작업이 많아질수록 콜백 함수가 중첩되고 코드가 복잡해지는데, 이를 콜백 헬이라고 부릅니다.

// 각 함수는 비동기적으로 정보를 가져옵니다.
function getUserInfo(userId, callback) {
  setTimeout(() => {
    // 사용자 정보를 찾고,
    const user = {id: userId, name: 'uha', ...}
    // 데이터를 돌려줍니다.
    callback(user);
  }, 2000);
}

function getUserPosts(userId, callback) {
  setTimeout(() => {
    // 이 비동기 작업이 서버에서 실행되는 부분입니다.
    // 사용자가 작성한 게시글을 찾고,
    const posts = [
      {id: 1, content: 'hi', user: userId, ...}, 
      {id: 2, content: 'bye', user: userId, ...},
    ]
    // 데이터를 돌려줍니다.
    callback(posts);
  }, 2000);
}

getUserInfo(101010, (user) => {
    getUserPosts(user.id, (posts) => {
      console.log(posts);
}});

예를 들어, 내가 쓴 게시글을 불러온다고 했을 때, 우선 서버에서 사용자 정보를 가져오고, 사용자 정보를 바탕으로 해당 사용자가 작성한 게시글을 불러와야 합니다. '사용자 정보', '게시글' 모두 비동기로 동작하지만 두 번째 '게시글' 정보는 이전의 '사용자 정보'가 필요합니다. 즉, 동기적으로 가져와야 하죠. 이럴 때 콜백함수를 사용할 수 있습니다.

이런 작업이 더 복잡해지면 다음 코드 처럼 더 깊게 중첩되고, 중첩된 콜백 구조는 가독성과 유지보수가 어려워집니다.

getUserInfo(101010, (user) => {
    getUserPosts(user.id, (posts) => {
		getPostComment(post.id, (comments) => {
          getCommentReply(comment.id, (reply) => {
            getReplyUserInfo(reply.user, (user) => {
              ...
            }
          }
		}
	}
});

이를 해결하기 위해 등장한 것이 PromiseAsync/Await 입니다.

Promise는 콜백 함수 대신 체이닝 방식을 통해 가독성을 개선하고, Async/Await는 비동기 코드를 동기적으로 작성한 것처럼 만들어 코드의 흐름을 쉽게 파악할 수 있게 합니다.


Promise, Async/Await

Promise

Promise는 자바스크립트에서 비동기 작업을 처리하는 데 사용되는 객체로, 미래에 완료될 작업 결과를 약속합니다. 이를 통해 비동기 작업의 성공 또는 실패를 다뤄 적절한 후속 작업을 수행할 수 있게 하고, 콜백 헬 문제를 완화합니다.

📍

Promise의 내부 프로퍼티

Promise 객체는 내부적으로 state와 result 프로퍼티를 가집니다. 직접 접근할 수는 없고, .then, .catch, .finally 메서드를 통해 접근해 후속 작업을 등록할 수 있습니다.

  • state: Promise의 현재 상태를 나타냅니다.(pending, fulfilled, rejected)
  • result: Promise의 결과 값으로, 초기에는 undefined이고, resolve()reject()가 호출되면 넘겨 받은 값이 설정됩니다.

📍

Promise의 상태

Promise는 세 가지 상태를 가지고, Pending 이 후에 한 번 결정되면 변경되지 않습니다.
1. Pending(대기): 비동기 작업이 아직 완료되지 않은 초기 상태.
2. Fulfilled(이행): 비동기 작업이 성공적으로 완료된 상태.
3. Rejected(거부): 비동기 작업이 실패한 상태.

Promise가 생성되면 Pending(대기) 상태로 시작합니다. 즉, 생성과 동시에 비동기 작업을 처리합니다. 그리고 비동기 작업 콜백 함수를 실행하고 성공적으로 실행(fulfilled)했다면 resolve함수를, 실패하여 에러가 발생(rejected)했다면 reject함수를 호출하고 결과 값을 넘겨줍니다. 그리고 실행이 완료되었다면 결과와 무관하게 finally함수가 호출됩니다.

Promise는 비동기 작업 콜백 함수를 전달받고, 이 콜백 함수는 resolve, reject 함수를 전달받습니다.
resolve()는 비동기 작업이 성공할 경우 결과를 반환할 콜백 함수이고, rejcet()는 비동기 작업이 실패할 경우 결과를 반환할 콜백함수 입니다.

// Promise는 객체이므로 new 키워드나 생성자를 사용해 생성합니다.
const promiseEx = new Promise((resolve, reject) => {
  // 콜백 함수 내에서 처리할 비동기 작업을 작성합니다.
  setTimeout(() => {
    const name = prompt('이름을 입력해 주세요');
    // 성공시 입력 받은 name값을 resolve함수로 넘겨줍니다.
    if(name) resolve(name)
    // 실패시 '게스트'라는 값을 reject함수로 넘겨줍니다.
    else reject('게스트');
  }, 2000);
})

                                
promiseEx
	.then((result) => console.log(result)) // 성공 시 실행 👉🏻 'uha'
	.catch((error) => console.log(error)) // 실패 시 실행 👉🏻 '게스트'
	.finally(() => console.log('끝!')) // 완료 후 실행 👉🏻 '뭐가됐든 끝!'
이름을 입력한 경우입력하지 않은 경우

Promise 객체가 생성되면서 전달된 콜백 함수가 즉시 실행됩니다. 생성된 Promise는 기본적으로 Pending(대기) 상태에 있습니다. 콜백 함수를 실행해서 내부에서 resolve | reject가 호출되면 상태가 바뀝니다. 이렇게 상태가 바뀌면 등록된 then, catch, finally 메서드의 콜백 함수가 마이크로태스크 큐에 들어갑니다.

이 후 호출 스택이 비면 이벤트 루프가 마이크로태스크 큐에서 작업을 가져와 실행합니다.

Promise chaining

Promise는 then() 메서드를 이용해 작업을 연속적으로 처리할 수 있습니다. 즉, Promise 객체로 메서드 체이닝을 하는 것입니다.

📍

Method Chagining(메서드 체이닝)

자바스크립트와 같은 객체 지향 프로그래밍에서 하나의 객체에 연속적으로 여러 메서드를 호출하는 기술입니다. 메서드를 호출할 때마다 자기 자신(this)을 반환하며 다음 메서드를 연결해서 호출할 수 있도록 설계된 패턴입니다. 객체 내부 상태를 메서드 호출을 통해 안전하게 변경하고, 코드를 간결하고 직관적으로 보이게 합니다.

class Person {
  constructor(name) {
    this.name = name;
  }
  setName(newName) {
    this.name = newName;
    return this;
  }
  greet() {
    console.log(`hi! i'm ${this.name}`);
    return this;
  }
}
// 메서드는 this를 반환하므로 체이닝이 가능합니다.
const person = new Person('uha');
person.setName('wHa').greet().setName('NewHa').greet();

이는 자바스크립트 배열 메서드에도 적용됩니다. map, filter, reduce와 같은 배열 메서드는 다시 배열을 반환하므로 메서드 체이닝이 가능합니다.

const result = [1, 2, 3, 4, 5]
	.filter(num => num % 2 === 1) // [1, 3, 5]
	.map(num => num * 2) // [2, 6, 10]

then(), catch()와 같은 메소드가 리턴하는 값은 여전히 Promise입니다. 따라서 해당 리턴 값에 다시 then, catch 메소드를 연달아 사용할 수 있고 거기서는 이전 Promise에서 리턴한 값을 사용하게 됩니다.

promiseData
	.then((result) => {
  		console.log(result); // 'uha'
  		return result + '님, 환영합니다!'
	})
	.then((result) => console.log(result)) // 'uha님, 환영합니다!'
	.catch((error) => console.log(error))

위와 같이 결과로 받은 값에 추가적인 과정을 거쳐 값을 return하고 해당 값을 받아서 작업을 또 할 수 있게 됩니다. 즉, Promise의 같은 객체에 메소드를 연속적으로 호출하는 것을 프로미스 체이닝이라고 합니다.

function getUserInfo(userId) {
  return new Promise((resolve, reject) => {
    try {
      setTimeout(() => {
   		 const user = {id: userId, name: 'uha', ...}
   		 resolve(user);
  	  }, 2000);
   	}catch (err) {
      reject(err)
    }
  }
}

function getUserPosts(userId) {
   return new Promise((resolve, reject) => {
     try {
       setTimeout(() => {
         const posts = [
           {id: 1, content: 'hi', user: userId, ...}, 
           {id: 2, content: 'bye', user: userId, ...},
         ];
         resolve(posts);
       }, 2000);
    } catch (err) {
       reject(err);
    }
  }
}

getUserInfo(101010)
  .then(user => {
    console.log(user); // {id: 101010, name: 'uha', ...}
    return getUserPosts(user.id);
  }
  .then(posts => {
    console.log(posts); //[ {id: 1, content: 'hi', user: userId, ...}, {id: 2, content: 'bye', user: userId, ...},];
  }

Async/Await

자바스크립트의 비동기 작업을 더 간결하고 읽기 쉽게 작성할 수 있도록 도와주는 Promise의 Syntax Sugar(문법적 설탕)입니다. 기본적으로 Promise를 기반으로 동작하며, 비동기 작업을 동기적으로 작성한 것처럼 표현할 수 있게 해줍니다.

📍

Syntax Sugar(문법적 설탕)

프로그래밍 언어에서 개발자가 코드를 더 간결하고 읽기 쉽게 작성할 수 있도록 제공하는 문법적인 기능입니다. 기능적으로는 기존 코드와 동일한 동작을 수행하지만, 더 직관적이고 간결한 표현을 가능하게 해 가독성을 높여 주는 개발자 편의를 위해 추가적으로 제공하는 표현 방법을 말합니다.

  • 화살표 함수 : 기존 함수 선언을 간결하게 표현해 가독성을 높여줍니다.
function add(a, b) { return a + b };
// 화살표 함수를 사용하면 더 직관적으로 함수 내용을 알 수 있습니다.
const add = (a, b) => a + b;
  • 구조 분해 할당 : 객체나 배열의 값을 한 번에 추출해서 사용할 수 있게 합니다.
const person = {name: 'uha', age: 30};
const name = person.name;
const age = person.age;
// 구조 분해 할당을 사용하면, 한 번에 name, age를 추출해 변수에 담을 수 있습니다.
const {name, age} = person;

Async는 비동기 작업을 처리하는 함수를 정의할 떄 사용합니다. 함수 앞에 async 키워드를 붙이면 항상 Promise를 반환하는 함수가 됩니다. return값이 일반 값이어도 Promise.resolve(value)로 반환됩니다.

Await은 Async함수 내부에서 Promise가 처리될 때까지 함수의 실행을 중단하고 비동기 작업의 결과를 기다리는 역할을 합니다.

// Promise 방식
fetch("https://jsonplaceholder.typicode.com/posts/1")
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((error) => console.error(error)); // 👈🏻 에러 처리

// Async/Await 방식
async function fetchData() {
  try {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
    const data = await response.json();
    console.log(data);
  } catch (error) { // 👈🏻 에러 처리
    console.error(error);
  }
}
fetchData();

async 함수가 호출되면 즉시 Promise 객체를 반환하고 함수 내부에서 await을 만나면 해당 Promise가 처리될 때까지 대기합니다. 호출 스택이 비어있으면 이벤트 루프가 Promise의 결과를 처리합니다.

async/await으로 작성하면 비동기 코드를 동기 코드처럼 작성할 수 있어서 가독성이 높아지고, 에러 처리가 간결해 집니다. 코드 구조가 단순해서 디버깅과 유지보수가 편리합니다.

하지만, await은 기본적으로 작업을 순차적으로 처리하고 함수 실행을 중단하므로 너무 많이 사용하면 성능에 영향을 줄 수 있습니다. 따라서 병렬로 비동기 작업을 실행하려면 Promise.all과 같은 Promise의 정적 메서드 사용을 고려합니다.

에러 처리

try...catch 블록을 사용해 비동기 작업에서 발생한 에러를 처리합니다.
Promise의 .catch()
구조와 동작
.catch()는 Promise 체이닝의 마지막에 위치하여, Promise에서 발생한 에러를 처리합니다.
체이닝 중간에 에러가 발생하면 .catch()에서 이를 감지하여 처리합니다. 간결하지만 체이닝이 길어지면 코드가 복잡해질 수 있습니다.

Async/Await의 try...catch
구조와 동작
try...catch는 동기 코드와 유사한 구조로 비동기 작업에서 발생하는 에러를 처리합니다.
try 블록에서 에러가 발생하면 catch 블록에서 이를 처리합니다. 코드가 순차적으로 실행되는 것처럼 보이므로 가독성이 뛰어나고 로컬 범위(블록)에서 에러를 처리하므로 코드 구조가 단순해집니다.

특징 Promise의 .catch() Async/Await의 try...catch
코드 스타일 체이닝 방식으로 작성 동기 코드처럼 작성
가독성 체이닝이 길어지면 복잡해질 수 있음 코드 흐름이 명확하고 읽기 쉬움
에러 처리 범위 체이닝 전체에서 발생한 에러를 처리 try 블록 내에서 발생한 에러만 처리
병렬 처리 병렬 작업에 적합 (Promise.all과 자연스럽게 연결) 추가적인 Promise.all과 함께 사용해야 함
에러 전파 자동으로 .catch()로 전달 명시적으로 throw해야 전파 가능
코드 간결성 간결하지만 체이닝이 길어지면 복잡할 수 있음 간결하면서도 직관적

Promise의 정적메서드

Promise.resolve()

주어진 값을 가지고 이행되는 프로미스 객체를 생성하는 역할을 합니다. 비동기 작업을 수행하고 결과를 Promise로 다루고 싶을 때 유용합니다.

const promiseEx = Promise.resolve(1000); // 1000값을 가지고 이행되는 Promise를 생성
console.log(promiseEx); // Promise{state: fulfilled, result: 1000}
promiseEx
  .then(result => result / 2) // 500
  .then(result => result / 100) // 5
  .then(console.log) // '5'를 출력합니다.

Promise.reject()

주어진 값을 가지고 거부된 상태의 프로미스 객체를 생성하는 역할을 합니다. 프로미스의 에러를 반환할 때 사용합니다.

const promiseEx = Promise.reject('err!!!');
console.log(promiseEx); // Promise{state: rejected, result: 'err!!!'}
promiseEx.catch(console.error); // err!!!
-------
// fetch하는 과정에서 이렇게 사용합니다.
function fetchData() {
  return fetch('url')
  	.then(response => response.json())
    .then(data => {
    	if(data.length === 0) Promise.reject('데이터없음!');
    	return data;
  	}
}
fetchData()
  .then(console.log) // 성공한 경우 데이터를 출력!
  .catch(console.error) // 실패한 경우 '데이터 없음!' 출력

Promise.all()

여러 Promise를 동시에 병렬로 실행할 때 사용하는 메서드입니다. 모든 Promise가 Fulfilled상태가 되면 그 결과 값을 배열로 반환하고, 하나라도 Rejected상태가 되면 전체가 실패하고 거부된 Promise만 반환합니다. 모든 작업이 완료되었을 때, 결과를 한 번에 다룰 때 유용합니다.

const promise1 = Promise.resolve(1000);
const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
  	resolve(2000)
  },2000)
}

Promise.all([promise1, promise2])
  .then(console.log) // [1000, 2000]
  .catch(console.error)
----------------------
const promise3 = Promise.new Promise((resolve, reject) => {
  setTimeout(() => {
  	reject('실패!')
  },3000)
};
Promise.all([promise1, promise2, promise3])
  .then(console.log)
  .catch(console.error) // 실패!

Promise.allSettled()

여러 개의 프로미스를 동시에 실행하고 모든 프로미스가 이행되거나 거부될 때까지 기다립니다. 이행/거부 상태를 가리지 않고 기다려서 각 결과를 배열로 반환합니다.

Promise.allSettled([promise1, promise2, promise3])
	.then(console.log) // [ {status:fulfilled, value: 1000}, {status: fulfilled, value: 2000}, {status: rejected, value: '실패!'} ]

Promise.any()

여러 개의 프로미스를 동시에 실행하고 그 중 하나라도 이행하면 해당 프로미스의 값을 반환합니다. 모든 프로미스가 거절될 때만 전체 프로미스가 거부됩니다.
가장 먼저 Fulfilled상태가 된 Promise의 결과를 반환하고, 모든 Promise가 실패하면 에러를 반환합니다.

Promise.any([promise1, promise2, promise3])
	.then(console.log) // 1000 
	.catch(console.error) // 만약 모든 promise가 실패한 경우, AggregateError: All promises were rejected 에러가 출력됩니다.

Promise.race()

가장 빨리 처리되는 프로미스 값을 반환
성공 또는 실패 여부에 관게없이 가장 먼저 완료된 Promise의 결과를 반환합니다.

Promise.race([promise1, promise2, promise3])
	.then(console.log) // 1000 
	.catch(console.error)
---
// 만약 promise1이 3000ms가 걸리고, promise3이 300ms가 걸린다면
Promise.race([promise1, promise2, promise3])
	.then(console.log) 
	.catch(console.error) // '실패!' 👈🏻 Promise3이 가장 빨리 끝나므로
profile
백 번을 보면 한 가지는 안다 👀

0개의 댓글