TIL - 5회차

2021.03.06

  • Promise

Promise


Promise 란?


프로미스란 자바스크립트 비동기 처리에 사용되는 객체이다.
Promise를 사용하여 비동기 작업이 (성공 혹은 실패로) 완료된 후의 결과를 받을 수 있다.

JS 비동기 처리 : 특정 코드의 실행이 완료될 때까지 기다리지 않고 다음 코드를 먼저 수행하는 자바스크립트의 특성을 의미한다.

let promise = new Promise(function(resolve, reject) {
  //실행 코드
});

promise 객체는 new키워드로 생성합니다. new Promise에 전달되는 함수는 executor(실행자, 실행 함수)라 한다.
executor는 new Promise가 만들어질 때 자동으로 실행되는데, 결과를 최종적으로 만들어내는 제작 코드를 포함한다.
executor의 인수 resolve와 reject는 자바스크립트가 자체적으로 제공하는 콜백이다.

  • resolve(value) - 일이 성공적으로 끝난 경우, 그 결과를 나타내는 value와 함께 호출
  • reject(error) - 에러 발생 시 에러 객체를 나타내는 error와 함께 호출
  • 대신 executor에선 결과를 즉시 얻든, 늦게 얻든 상관없이 인수로 받은 콜백 중 하나의 콜백은 반드시 호출해야 한다.

프로미스의 상태(States)

프로미스는 총 4개의 상태를 갖는다.
상태란 프로미스의 처리 과정을 의미한다. new Promise()객체로 프로미스를 생성하고 종료될 때까지 3가지 상태(pending, fulfilled, rejected)를 갖는다.

상태의미구현
pending(대기)비동기 처리가 아직 수행되지 않은 상태resolve 또는 reject 함수가 아직 호출되지 않은 상태
fulfilled(성공)비동기 처리가 수행된 상태resolve 함수가 호출된 상태
rejected(실패)비동기 처리가 수행된 상태reject 함수가 호출된 상태
settled(성공 또는 실패)비동기 처리가 수행된 상태resolve 또는 reject 함수가 호출된 상태
  • 한번 Setteld(처리)된 값은 재실행 할 수 없다.
  • state —> 처음엔 "pending"(대기)이었다 resolve가 호출되면 "fulfilled", reject가 호출되면 "rejected"로 변합니다.
  • result —> 처음엔 undefined이었다, resolve(value)가 호출되면 value로, reject(error)가 호출되면 error로 변합니다.

Pending(대기)

new Promise()메서드를 호출하면 대기 상태가 된다.

new Promise();

Fulfilled(성공)

resolve를 실행하면 성공 상태가 된다.

new Promise(function(resolve, reject) {
  resolve();
});

Rejected(실패)

reject를 호출하면 실패 상태가 된다.

new Promise(function(resolve, reject) {
  reject();
});

  • 프로미스 흐름도 - 출처 : MDN

사용

.then, .catch, .finally 메서드를 이용해 응답에 따라 분기 처리 하여 Promise 응답 결과를 사용한다.

resolve시 then으로

.then은 프라미스에서 가장 중요하고 기본이 되는 메서드이다.

let promise = new Promise(function(resolve, reject) {
  //실행 코드
});

promise.then(
  function(result) { /* 결과(result)를 다룹니다 */ },
  function(error) { /* 에러(error)를 다룹니다 */ }
);

//예시
let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve("done!"), 1000);
});

// resolve 함수는 .then의 첫 번째 함수(인수)를 실행합니다.
promise.then(
  result => alert(result), // 1초 후 "done!"을 출력
  error => alert(error) // 실행되지 않음
);

작업이 성공적으로 처리된 경우만 다룰 경우 .then에 인수를 하나만 전달한다.

let promise = new Promise(resolve => {
  setTimeout(() => resolve("done!"), 1000);
});

promise.then(alert); // 1초 뒤 "done!" 출력

reject시 catch로

에러가 발생한(실패한) 경우만 다루고 싶다면 .catch를 사용한다.

.then(null, errorHandlingFunction)같이 null을 첫 번째 인수로 전달해도 된다. .catch.thennull을 전달하는 것과 동일하게 작동한다.

//예시
let promise = new Promise(function(resolve, reject) {
  setTimeout(() => reject(new Error("에러 발생!")), 1000);
});

// reject 함수는 .then의 두 번째 함수를 실행합니다.
promise.then(
  result => alert(result), // 실행되지 않음
  error => alert(error) // 1초 후 "Error: 에러 발생!"를 출력
);
//catch
let promise = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("에러 발생!")), 1000);
});

// .catch(f)는 promise.then(null, f)과 동일하게 작동합니다
promise.catch(alert); // 1초 뒤 "Error: 에러 발생!" 출력

finally

프라미스가 처리되면(성공 또는 실패) 콜백함수가 항상 실행된다는 점에서 .finally(f)호출은 .then(f,f)와 유사하다.
결과가 어떻든 응답이 필요하면 finally가 유용하다.

new Promise((resolve, reject) => {
  /* 비동기 처리를 수행하고, 그 후 resolve·reject를 호출함 */
})
  // 성공·실패 여부와 상관없이 프라미스가 처리되면 실행됨
  .finally(() => 로딩 인디케이터 중지)
  .then(result => result와 err 보여줌 => error 보여줌)

여기서 중요한 점은 .then메소드는 다시 Promise를 반환한다. Promise 객체를 반환한다는 것은 .then, .catch메소드를 사용할 수 있다는 것을 뜻하며, 이를 통해 연속적으로 .then메소드를 사용하여 Promise chaining이 가능하다는 것을 의미한다.(.catch메소드 이후에도 chaining이 가능)

여러 개의 프로미스 연결 (Promise chaining)

function getData() {
  return new Promise({
    // ...
  });
}

// then() 으로 여러 개의 프로미스를 연결한 형식
getData()
  .then(function(data) {
    // ...
  })
  .then(function() {
    // ...
  })
  .then(function() {
    // ...
  });

//예시
new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000); // (*)

}).then(function(result) { // (**)

  alert(result); // 1
  return result * 2;

}).then(function(result) { // (***)

  alert(result); // 2
  return result * 2;

}).then(function(result) {

  alert(result); // 4
  return result * 2;

});

위 코드는 프로미스 객체를 하나 생성하고 setTimeout()을 이용해 1초 후에 resolve()를 호출하는 예제이다.

Promise.all

Promise.all() 메서드는 순회 가능한 객체에 주어진 모든 프로미스가 이행한 후, 혹은 프로미스가 주어지지 않았을 때 이행하는 Promise를 반환합니다. 주어진 프로미스 중 하나가 거부하는 경우, 첫 번째로 거절한 프로미스의 이유를 사용해 자신도 거부합니다.

Promise.allPromise 인스턴스들이 담긴 배열을 인자로 받아 사용하는데,
배열의 모든 요소가 Promise 인스턴스일 필요는 없다.

const promise1 = Promise.resolve(3);
const promise2 = 42; // 프로미스가 아닌 값
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

//배열을 인자로 받음
Promise.all([promise1, promise2, promise3]).then(values => {
  console.log(values); // Array [3, 42, 'foo']
});

// resolve되는 값들을 destructuring 할 수 있다.
Promise.all([promise1, promise2, promise3]).then(([one, two, three]) => {
  console.log(one); // 3
  console.log(two); // 42
  console.log(three); // 'foo'
});

//다른 예시
const emptyPromiseAll = Promise.all([]); // 빈 배열을 동기적 실행
const promiseA = Promise.all([1337, "hi"]); // 프로미스가 아닌 값은 무시하지만 비동기적으로 실행됨
const promiseB = Promise.all([1, 2, 3, Promise.resolve(444)]); // 위와 동일

p; // [] 동기적 실행히라 then 메소드 사용하지 않아도 된다. 
p2.then(res => console.log(res)); // [1337, 'hi']
p3.then(res => console.log(res)); // [1, 2, 3, 444]

Promise 실무사례 예시

getData(userInfo)
  .then(parseValue)
  .then(auth)
  .then(diaplay);
//userInfo(사용자 정보)를 받아와 파싱, 인증, 등의 작업을 수행하는 코드
var userInfo = {
  id: '[email protected]',
  pw: '****'
};

function parseValue() {
  return new Promise({
    // ...
  });
}
function auth() {
  return new Promise({
    // ...
  });
}
function display() {
  return new Promise({
    // ...
  });
}
//parseValue, auth, display는 각각 프로미스를 반환해주는 함수라고 가정

프로미스 에러 처리 방법

위에서 언급한 것 처럼 2가지 방식이 있다.
1. .then(null, errorHandlingFunction)와 같이 .then의 두 번째 인자로 에러 처리
2. .catch를 이용한 에러 처리

가급적 catch()를 사용하여 에러를 처리하는 게 더 효율적이다.

// then()의 두 번째 인자로는 감지하지 못하는 오류
function getData() {
  return new Promise(function(resolve, reject) {
    resolve('hi');
  });
}
getData().then(function(result) {
  console.log(result);//hi
  throw new Error("Error in then()"); // Uncaught (in promise) Error: Error in then()
}, function(err) {
  console.log('then error : ', err);
});

resolve()메서드를 호출하여 정상적으로 처리했지만, .then의 첫 번째 인자 내부에서 발생한 오류는 제대로 잡아내지 못한다.
Uncaught (in promise) Error: Error in then()에러를 잡지 못했다는 에러가 발생한다.
하지만 .catch로 처리하면 다른 결과가 나온다.

// catch()로 오류를 감지하는 코드
function getData() {
  return new Promise(function(resolve, reject) {
    resolve('hi');
  });
}
getData().then(function(result) {
  console.log(result); // hi
  throw new Error("Error in then()");
}).catch(function(err) {
  console.log('then error : ', err); // then error :  Error: Error in then()
});

더 많은 예외 처리 상황을 위해 catch()를 통해 에러를 처리 하자

좋은 웹페이지 즐겨찾기