[JS] Promise with Multiple Promises

colki·2021년 5월 11일
0
post-custom-banner

Udemy_JavaScript: The Advanced JavaScript Concepts (2021) 강의를 바탕으로 메모한 내용입니다.

Promise

promise is an object that may produce a single value sometime in the future either resolved or rejected with a reason.

프로미스 객체는 미래의 성공 또는 실패일 때 일어날 결과를 리턴하겠다는 약속이다.

또한 자바스크립트에 내장된 객체이기 때문에 .then, .catch 등의 메서드를 사용할 수 있다.

const promise = new Promise(function () {...})

프로미스 생성자 함수는 함수를 인자로 받는다. 이 함수의 매개변수가 resolve, reject 되시겠다. 그런데 얘네도 함수다.


state

한 번이라도 성공했거나 실패한 프로미스는 초기 상태로 돌아간다.

프로미스 생성자 함수에 인자로 들어간 함수 내부에서 우리는 비동기 작업을 하고, 비동기 작업이 성공할 경우 resolve를 실행해야 하고, 실패할 경우 reject를 실행해야 한다.

  • pending : 아직 결과가 정해지지 않은 상태
  • fulfilled(resolve) : 성공한 상태
  • rejected : 실패한 상태
const promise = new Promise((resoleve, reject) => {
  if (condition) {
    resolve('stuff worked');
  } else {
  reject('Error);
  }
});

▪ pending // undefined

function fetchUser() {
  return new Promise((resolve, reject) => {
});
}

// Promise {<pending>}
// [[PromiseState]]: "pending"
// [[PromiseResult]]: undefined

위 상태에서는 promise가 pending만 되어있고 아무 일도 하지 않는다.

function fetchUser() {
  return new Promise((resolve, reject) => {
    resolve('hi');
});
}

var user = fetchUser();
console.log(user);

// Promise {<fulfilled>: "hi"}
// [[PromiseState]]: "fulfilled"
// [[PromiseResult]]: "hi"

위와같이 resolve 처리를 해주면 promise가 fulfilled로 바뀐다.

만약에 setTImeout이었다면 시간이 지나기 전은 pending, 시간이 지나고 resolve, reject 함수가 실행한 다음이면 상태가 성공/실패로 바뀐다.

▪ Write Promise


function fetchUser() {
  return new Promise((resolve, reject) => {
  	...
}

fetchUser().then(...const fetchUser = new Promise(function(resolve, reject) {
  ...
};
  
fetchUser.then(...// 익명  
new Promise(function(resolve, reject) {
	...
}.then(...

▪ Example_fetch

Reference JSON Placeholder urls

const urls = [
  'https://jsonplaceholder.typicode.com/users', // 10users
  'https://jsonplaceholder.typicode.com/posts', // 100posts
  'https://jsonplaceholder.typicode.com/todos' // 200todos
]

Promise.all(urls.map(url => {
 return fetch(url).then(response => response.json())
})).then(results => {
  console.log(results[0])
  console.log(results[1])
  console.log(results[2])
}).catch(() => console.log('error'));

// users (10)[{...}, ... , {...}]
// posts (100)[{...}, ... , {...}]
// todos (200)[{...}, ... , {...}]

Remember the fetch simply returns a promise if I just run fetch here.
You see that I get a promise that's pending.

콘솔에 fetch('해당 url')을 찍으면 다음과 같이 출력된다.

![https://velog.velcdn.com/images%2Fcolki%2Fpost%2Ff26ecc68-7ab9-4da6-854c-06aa4c318a89%2FUntitled.png%5D(https%3A%2F%2Fimages.velog.io%2Fimages%2Fcolki%2Fpost%2Ff26ecc68-7ab9-4da6-854c-06aa4c318a89%2FUntitled.png)

💡 Body.json()

Body json() 메서드는 Response 스트링을 가져와 스트링이 완료될때까지 읽는다. 이 메서드는 body 텍스트를 JSON으로 바꾸는 결과로 해결되는 promise를 반환한다.

Return value
A promise that resolves with the result of parsing the body text as JSON.
This could be anything that can be represented by JSON — an object, an array, a string, a number... MDN


Producer & Consumer

promise를 리턴하는 부분을 Producer,
State를 만드는 부분을 Consumer라고 나눈다.

Producer
promise를 만드는 순간 executor(callback)함수가 바로! 자동으로! 실행된다.

Consumer
resolve().then
reject().then.catch

//실패시
const promise = new Promise((resolve, reject) => {
  //doing some heavy work(network, read files)
  console.log('hi')
  setTimeout(() => {
    //resolve('colki');
    reject(new Error('no network'))
  }, 2000);
});


promise.then((value) => {
  console.log(value);
});

// hi
// (2초 후) Uncaught (in promise) Error: no network

⇒ then으로 성공적인 케이스에 대해서만 대응했기 때문에 uncaught에러가 발생!.
아래와 같이 catch메서드를 사용해서 에러핸들링을 해주어야 한다.

promise
  .then((value) => {
    console.log(value);
  })
  .catch(error => {
    console.log(error);
  })
  .finally(() => {
    console.log('end');
  });
  
// hi
// (2초 후) Error: no network  
// end

finally는 성공과 실패 상관없이 무조건 마지막에 호출된다.


Promise Chaining

then은 값을 바로 전달해도 되고 또 다른 비동기인 promise를 전달해도 된다.

그래서 .then .then .then .catch 이렇게 여러개를 묶어서 사용 가능하다.
catch메서드로 앞서 나오는 에러를 한 번에 컨트롤할 수 있다.

자바스크립트에서 비동기 처리를 할 때는 콜백지옥을 만들어서 각각의 비동기함수마다 (err,data){ ... 식으로 처리해줘야 했다.

하지만 Promise가 도입되면서 then, catch를 사용해서 본래 자바스크립트에서 에러처리를 할 때처럼 native한 방식(try, catch)으로 다룰 수 있게 된 것이다.

개발자들에게 친숙한 에러핸들링일 뿐 아니라 성공로직과 실패로직을 명확하게 분리해서 핸들링할 수 있다.

또한 기존에는 비동기함수의 결과를 받을 때까지 아무것도 하지 못하고 마냥 기다려야 했다면, Promise인스턴스 객체를 반환해서 다른 함수에 넘겨주거나 자료구조에 담는 등 많은 일을 할수 있도록 길이 확장된 것이다.

그래서 Promise는 자바스크립트가 가지고 있던 제약에서 벗어나서, 수동적으로 처리하던 비동기를 동기적 흐름과 유사하게, 능동적으로 제어할 수 있게 해주는 역할을 한다. 우리가 쥐락 펴락 하기 수월해졌달까.
(그저 콜백지옥을 해결하기 위해 Promise가 나왔다? 는 비약으로 볼 수 있다)

#가독성 #확장성 #다양성 #능동적 #단순히 콜백지옥의 해결을 위해서 사용? #NO! 

.finally

finally 메서드는 promise 객체를 리턴한다. promise가 resolve되거나 reject 되는 것 관계 없이 finally에 지정한 콜백함수가 무조건 실행된다.

promise 결과에 상관없이 무조건 한 번은 실행된다는 소리!

  • .finally(콜백함수)
  • .then 과.catch 구문의 코드 중복을 피하게 해준다.
  • finally 콜백함수는 어떠한 인자도 전달받지 않는다.
    왜냐, promise가 성공했는지 실패했는지 여부를 판단할 수 없기 때문이다. 그래서 그걸 몰라도 되는 경우 쓰면 좋다.

const urls = [
  'http://swapi.dev/api/people/1',
  'http://swapi.dev/api/people/2',
  'http://swapi.dev/api/people/3',
  'http://swapi.dev/api/people/4'
];

Promise.all(urls.map(url => {
  return fetch(url).then(people => people.json())
}))
  .then(people => {
    console.log('1', people[0])
    //throw Error; 
    console.log('2', people[1])
    console.log('3', people[2])
    console.log('4', people[3])
  })
  .catch(err => console.log('uhh fix it!', err))
  .finally(() => {console.log('final')})  
  
  //final

에러 코드를 넣든 안넣든 콜백함수가 무조건 실행되기 때문에, 항상 final이 출력된다.

Multiple Promises


multiple promises를 처리하는 3가지 방법이 있다.

  • parallel (start all at the same time)
  • sequence (they're all dependent on each other)
  • race (one comes back first and ignores the rest.)


// 3개의 Promise를 각 방법으로 돌려보자.
const promisify = (item, delay) =>
  new Promise((resolve) =>
    setTimeout(() =>
      resolve(item), delay));

const a = () => promisify('a', 100);
const b = () => promisify('b', 5000);
const c = () => promisify('c', 3000);

parallel

1번, 2번, 3번 동시에 타이머 시작!


async function parallel() {
  const promises = [a(), b(), c()]; // [Promise {}, Promise {}, Promise {}]
  const [output1, output2, output3] = await Promise.all(promises);

  return `parallel is done: ${output1} ${output2} ${output3}`;
}

parallel().then(console.log);
// parallel is done: a b c

sequence

1번 끝나면 다음 2번 끝나면 다음 3번 끝나면 뙇!


지저분할 수 있는 Promise Chaining을 보기 좋게 만들어주는 nice한 방법.

const output1 = await a();
// a값이 리턴될 때까지 동작을 멈추고 있음 리턴값나오면
// 다음 구문 b값 리턴될 때까지 또 멈추고 기다림~ 반복~

waiting for all the things to settle and then finally eventually we'll get sequences done a b c.

async function sequence() {
  const output1 = await a();
  const output2 = await b();
  const output3 = await c();

  return `sequence is done ${output1} ${output2} ${output3}`;
}

sequence().then(console.log);
// sequence is done a b c

race

제일 먼저 1등으로 들어오는 놈만 뙇!


whichever one returns first. I wanted to be upper one and just log that and ignore all the other ones

async function race() {
  const promises = [a(), b(), c()]; // [Promise {}, Promise {}, Promise {}]
  const output1 = await Promise.race(promises);
  return `race is done ${output1}`;
}

race().then(console.log);
// race is done a

누가 제일 빠를까?

parallel().then(console.log);
sequence().then(console.log);
race().then(console.log);

// race is done a (100ms 후)
// parallel is done: a b c (500ms후)
// sequence is done a b c (8100ms 후)

Promise.allSettled

const promiseOne = new Promise((resolve, reject) =>
  setTimeout(resolve, 3000));
const promiseTwo = new Promise((resolve, reject) =>
  setTimeout(reject, 3000));

Promise.all([promiseOne, promiseTwo]).then(console.log);

3000ms후에 resolve와 reject를 실행하는 두 코드가 공존할 때
무슨 일이 벌어질까?

Promise.all() 안에는 성공의 케이스만 들어가야 하는데, 현재 resolve일때, reject일때 두 경우가 함께 들어있고 또한 .catch구문이 없어서 에러핸들링을 못 해주고 있다.




⏬ 리팩토링: all → allSettled & add .catch


Promise.allSettled([promiseOne, promiseTwo])
  .then(console.log)
  .catch(err => console.log('something failed', err));


promise.allSettled로 메서드를 바꿔주니 fulfilled value와 reject reason 의 각 state 담긴 Promise 객체가 리턴되었다.

그렇다면, all()과 allSettled() 두 메서드의 차이는 무엇일까?

Promise.all()

여러 Promise 의 성공/실패 여부에 따라서 결과가 달라진다.

  • 모두 성공일 때 or Promise 가 없는 경우 : Promise 객체가 담긴 배열을 리턴한다.

  • 하나라도 실패일 때 : reject일때의 reason을 전달한다.

Promise.allSettled()

runs regardless of whether they reject or not.

성공 또는 실패의 결과를 모아서 Promise 객체가 담긴 배열을 리턴한다.
fulfilled,상태로 전달되면 value 속성이 전달되고, rejected 상태로 전달 시 reason 속성으로 전달된다.

그래서 각 Promise가 어떻게 이행(또는 거부)됐는지 value 속성 및 reason 속성을 통해 알 수 있다.

profile
매일 성장하는 프론트엔드 개발자
post-custom-banner

0개의 댓글