Promise 객체와 Promise All 의 사용

Promise

비동기 작업을 순차적으로 실행하기 위하여 사용되는 객체
그러나 단지 callback함수 대신 보기 쉽게 만들어주는 것. 실행되지 않았지만 결과를 받는 처리(약속)

- 상태: 3가지
  1. Pending (대기)
  2. Fulfilled (완료)
  3. Rejected (실패)

- then, catch, finally 3가지 메서드를 가진다.

  1. then : promise 객체가 fulfilled 상태가 되면 실행할 콜백함수를 등록하는 메서드
  2. catch : promise객체가 rejected 상태가 되면 콜백함수를 등록하는 메서드
  3. finally : 어떤 작업의 성공, 실패 여부와 상관없이 항상 실행하고 싶은 콜백함수를 등록

 

new Promise

new Promise 란 ? 
- promise 객체를 직접 생성할 수 있다.
- resolve : 생성될 promise 객체를 fulfilled 상태로 만들 수 있는 함수가 연결된다.
- reject : 생성될 promise 객체를 reject 상태로 만들 수 있는 함수가 연결된다. 
 

  • reject 
const p = new Promise((resolve, reject) => {
  setTimeout(() => { resolve('success'); }, 2000); // 1번) 2초후에 promise 객체가 fulfilled 상태가 되고, resolve(괄호)안에 값이
});

p.then((result) => { console.log(result); }); // 2번) 리턴값으로 넘어오게 된다. (result 값으로 넘어오는 것)

 

  • fulfilled
const p = new Promise((resolve, reject) => {
  setTimeout(() => { reject('fail'); }, 2000); // 1번) 2초후에 promise 객체가 rejected 상태가 되고, reject(괄호)안에 값이
});

p.catch((error) => { console.log(error); }); // 2번) 리턴값으로 넘어오게 된다. (error 값으로 넘어오는 것)

 

Promise.all(iterable)

  • 주로 배열을 받아 인자로 반복 가능한 객체들을 순회하며 비동기 작업들을 처리한다.
  • 입력 값으로 들어온 프로미스 중 하나라도 거부당하면 Promise.all()은 즉시 거부한다.
  • 만약 비동기 작업이 아니라 의미없는 [1,2,3] 와 같은 데이터를 인자로 넘겨주면, all 메서드는 다음과 같은 작업을 자동으로 수행한다.
Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]);

 
이는 내부적으로 Promise.resolve() 메서드가 호출되어 인자로 전달된 값이 프로미스 객체로 변환되기 때문.
빈 객체를 전달한 경우, 동기적으로 이미 이행한 프로미스를 반환한다.
 
 

Promise.all 의 동기성/비동기성

  • Promise.all은 비동기적으로 동작하지만, 만약 인자가 빈 객체라면 동기적으로 실행된다.
  • 즉, 실행하자마자 바로 값을 반환해버린다.
  • 이것은 거부일 때도 마찬가지 이다. 

 

Promise.all()의 실패 우선성

  • Promise.all()은 배열 내 요소 중 어느 하나라도 거부하면 즉시 거부합니다. 예를 들어, 일정 시간이 지난 이후 이행하는 네 개의 프로미스와, 즉시 거부하는 하나의 프로미스를 전달한다면 Promise.all()도 즉시 거부한다.
var p1 = new Promise((resolve, reject) => {
  setTimeout(() => resolve('하나'), 1000);
});
var p2 = new Promise((resolve, reject) => {
  setTimeout(() => resolve('둘'), 2000);
});
var p3 = new Promise((resolve, reject) => {
  setTimeout(() => resolve('셋'), 3000);
});
var p4 = new Promise((resolve, reject) => {
  setTimeout(() => resolve('넷'), 4000);
});
var p5 = new Promise((resolve, reject) => {
  reject(new Error('거부'));
});

// .catch 사용:
Promise.all([p1, p2, p3, p4, p5])
.then(values => {
  console.log(values);
})
.catch(error => {
  console.log(error.message)
});

// 콘솔 출력값:
// "거부"

 
 
Promise.all()은 Promise 로직을 병렬로 처리하는 메소드이다. async 함수를 사용한다고 가정해볼 때, async 함수에서 await 을 사용하면, Promise 작업이 resolve 될 때 까지 blocking 된다. 이와 달리, Promise.all()은 각 Promise 가 처리될 때 blocking 처리하지 않는다. 
 
 
언제 Promise All을 사용하는 것이 좋을까? 

Promise 작업 간 순서가 중요하지 않고,  병렬로 Promise 작업을 처리하고 싶을 때

여기서 비동기 처리란 , 순서가 보장되지 않아도 되는 상황을 말한다. 예를 들어 "유저를 조회하고, 거기서 얻은 유저 아이디를 통해 찜하기 상품을 가져온다." 와 같은 경우는 아니다. 순서와 무관한, 예를 들면 "상품 목록과 상품 목록의 총개수를 조회한다" 경우이다.
 
async await 은 함수를 하나하나 다 기다렸다가 하고, Promsie.all을 병렬로 비동기 함수로 실행시킨다. 

Async await 과 Promise all

Promise.all 은 차례대로 앞의 함수의 실행이 완료되는 것을 기다리지 않고, 비동기적 병렬로 실행된다. "순서대로 실행되지만, 앞의 함수가 완료되는 것을 기다리지 않는다". 그리고 마지막으로 완료되는 함수까지 기다렸다가, 값을 반환한다. 이 때 반환 순서는 실행 순서와 동일하게 준다. (완료 순서가 아니다) 
 
그래서 각각의 완료 값을 변수에 할당한다고 하면, 아래와 같이 적을 수 있다. 

const [res1, res2, res3] => await Promise.all([func1(), func2(), func3()])

 

여러 Promise 작업을 하나의 트랜잭션으로 처리할 때 : 빠른 처리와 Fail Fast 의 장점 

비동기처리를 병렬적으로 해서 더 빠르게 처리할 수 있다는 장점이 있다. 하지만 이때만 장점이 있는 게 아니라. 비동기 처리가 실패했을 때에도 있다. Promise.all() 함수를 사용한 경우, 비동기 함수가 병렬로 실행되기 때문에 실행 순서와는 상관이 없이 가장 빨리 에러를 반환하는 함수의 시간을 기준으로 동작이 종료된다.

물론 각각의 함수를 await 하는 경우에도 에러가 가장 빨리 뱉어지는 함수를 맨 위로 올리면 동일한 결과를 갖게 된다. 하지만 가장 에러를 빨리 뱉을 것 같은 함수를 찾아 맨 위에 적는다는 것은 사실상 어렵다. 또한 Promise.all() 이라는 대안이 있는데 굳이 그럴이 이유도 없는 것이다. 
 
따라서, Promise.all()은 하나의 Promise라도 reject(실패) 하면, catch() 분기문으로 넘어간다는 것을 기억해두면 병렬적 실행과, 특히 에러를 빠르게 뱉어내야 할 때 유용하게 사용할 수 있다.
 
 
 


ref :