[JavaScript] Promise

나지은·2020년 11월 26일
0
post-thumbnail

Promise란?

프로미스는 비동기 처리에 사용되는 객체입니다.

프로미스를 사용하면 비동기 프로그래밍을 할 때 코드를 순차적으로 작성이 가능하여 컨트롤이 쉬워지고 코드 가독성이 좋아집니다.

프로미스의 세 가지 상태

  • 대기(pending): 비동기 처리의 결과를 기다리는 중

  • 이행(fulfilled): 비동기 처리가 정상적으로 끝났고 결과값을 가지고 있음

  • 거부(rejected): 비동기 처리가 비정상적으로 끝났음

    이행, 거부 상태를 처리(settled) 상태라고 부르고, 처리 상태가 되면 더이상 다른 상태로 변하지 않는다.

프로미스 처리 흐름

출처: MDN Promise

프로미스 생성

let p = new Promise(function(resolve, reject) {
//executor
});

new Promise에 전달되는 함수는 executor(실행자 또는 실행 함수) 라고 부릅니다. executor는 new Promise가 만들어질 때 자동으로 실행되는데, 결과를 최종적으로 만들어내는 제작 코드를 포함합니다.

executor의 인수 resolve와 reject는 자바스크립트가 자체적으로 제공하는 콜백입니다. 개발자는 resolve와 reject를 신경 쓰지 않고 executor 안 코드만 작성하면 됩니다.

대신 executor에선 결과를 즉시 얻든, 늦게 얻든 상관없이 상황에 따라 인수로 넘겨준 콜백 중 하나를 반드시 호출해야 합니다.

  • resolve(value) — 일이 성공적으로 끝난 경우, 그 결과를 나타내는 value와 함께 호출
  • reject(error) — 에러 발생 시 에러 객체를 나타내는 error와 함께 호출

요약하면 다음과 같습니다. executor는 자동으로 실행되는데 여기서 원하는 일이 처리됩니다. 처리가 끝나면 executor는 처리 성공 여부에 따라 resolve나 reject를 호출합니다.

한편, new Promise 생성자가 반환하는 promise 객체는 다음과 같은 내부 프로퍼티를 갖습니다.

  • state — 처음엔 "pending"(대기)이었다 resolve가 호출되면 "fulfilled" 로 변하고, reject가 호출되면 "rejected"로 변합니다.
  • result — 처음엔 undefined이었다, resolve(value)가 호출되면 value로, reject(error)가 호출되면 error로 변합니다.

따라서 executor는 아래 그림과 같이 promise의 상태를 둘 중 하나로 변화시킵니다.

resolve

아래는 promise 생성자와 간단한 executor 함수로 만든 예시입니다. setTimeout을 이용해 executor 함수는 실행되는데 약간의 시간이 걸립니다.

let p = new Promise(function(resolve, reject) {
  // 프로미스가 만들어지면 executor 함수는 자동으로 실행됩니다.

  // 1초 뒤에 일이 성공적으로 끝났다는 신호가 전달되면서 result가 'done'이 됩니다.
  setTimeout(() => resolve("done"), 1000);
});

executor는 new Promise에 의해 자동으로 그리고 즉각적으로 호출됩니다.

executor는 인자로 resolve와 reject 함수를 받습니다. 이 함수들은 자바스크립트 엔진이 미리 정의한 함수이므로 개발자가 따로 만들 필요가 없습니다. 다만, resolve나 reject 중 하나는 반드시 호출해야 합니다.

executor '처리’가 시작 된 지 1초 후, resolve("done")이 호출되고 결과가 만들어집니다. 이때 promise 객체의 상태는 다음과 같이 변합니다.

이처럼 일이 성공적으로 처리되었을 때의 프로미스는 'fulfilled promise(약속이 이행된 프로미스)'라고 불립니다.

reject

아래는 executor가 에러와 함께 약속한 작업을 거부하는 경우입니다.

let promise = new Promise(function(resolve, reject) {
  // 1초 뒤에 에러와 함께 실행이 종료되었다는 신호를 보냅니다.
  setTimeout(() => reject(new Error("에러 발생!")), 1000);
});

1초 후 reject(...)가 호출되면 promise의 상태가 "거부(rejected)"로 변합니다.

resolve, reject 즉시 호출

executor는 대개 무언가를 비동기적으로 수행하고, 약간의 시간이 지난 후에 resolve/reject를 호출하는데, 꼭 이렇게 할 필요는 없습니다. 아래와 같이 resolve나 reject를 즉시 호출할 수도 있습니다.

let promise = new Promise(function(resolve, reject) {
  // 일을 끝마치는 데 시간이 들지 않음
  resolve(123); // 결과(123)를 즉시 resolve에 전달함
});

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

어떤 일을 시작했는데 알고 보니 일이 이미 끝나 저장까지 되어있는 경우, 이렇게 resolve나 reject를 즉시 호출하는 방식을 사용할 수 있습니다.

이렇게 하면 프로미스는 즉시 이행 상태가 됩니다.

then

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

promise.then(
  function(result) 
  function(error) 
);

.then의 첫 번째 인수는 프로미스가 이행되었을 때 실행되는 함수이고, 여기서 실행 결과를 받습니다.

.then의 두 번째 인수는 프로미스가 거부되었을 때 실행되는 함수이고, 여기서 에러를 받습니다.

프로미스가 성공적으로 이행된 경우 첫 번째 함수가 실행됩니다.

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

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

프로미스가 거부된 경우에는 두 번째 함수가 실행됩니다.

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

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

작업이 성공적으로 처리된 경우만 다루고 싶다면 .then에 인수를 하나만 전달하면 됩니다.

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

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

catch

에러가 발생한 경우만 다루고 싶다면 .then(null, errorHandlingFunction)같이 null을 첫 번째 인수로 전달하거나 .catch(errorHandlingFunction)를 쓸 수 있습니다. .catch.then에 null을 전달하는 것과 동일하게 작동합니다. .catch(f).then(null, f)과 같습니다.

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) 와 유사합니다. 쓸모가 없어진 로딩 인디케이터(loading indicators)를 멈추는 경우처럼, 마무리가 필요한 경우에 finally가 유용합니다.

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

하지만 finally(f)는 .then(f, f) 와 완전히 같진 않습니다. 차이점은 다음과 같습니다.

  1. finally 핸들러엔 인수가 없습니다. finally에선 프로미스가 이행되었는지, 거부되었는지 알 수 없습니다. finally에선 절차를 마무리하는 ‘보편적’ 동작을 수행하기 때문에 성공·실패 여부를 몰라도 됩니다.
  2. finally 핸들러는 자동으로 다음 핸들러에 결과와 에러를 전달합니다.

다음은 resultfinally를 거쳐 then까지 전달되는 경우입니다.

new Promise((resolve, reject) => {
  throw new Error("에러 발생!");
})
  .finally(() => alert("프로미스가 준비되었습니다."))
  .catch(err => alert(err)); // <-- .catch에서 에러 객체를 다룰 수 있음

finally는 프로미스 결과를 처리하는 것이 아니라, finally를 통해서 프로미스 결과를 전달됩니다.

  1. .finally(f)는 함수 f를 중복해서 쓸 필요가 없기 때문에 .then(f, f)보다 문법 측면에서 더 편리합니다.

Promise 의 5가지 메소드

  1. Promise.all(promises) – 모든 프로미스가 이행될 때까지 기다렸다가 그 결과값을 담은 배열을 반환합니다. 주어진 프로미스 중 하나라도 실패하면 Promise.all은 거부되고, 나머지 프로미스의 결과는 무시됩니다.
  2. Promise.allSettled(promises) – 최근에 추가된 메서드로 모든 프로미스가 처리될 때까지 기다렸다가 그 결과(객체)를 담은 배열을 반환합니다. 객체엔 다음과 같은 정보가 담깁니다.
    • status"fulfilled" 또는 "rejected"
    • value(프로미스가 성공한 경우) 또는 reason(프로미스가 실패한 경우)
  3. Promise.race(promises) – 가장 먼저 처리된 프로미스의 결과 또는 에러를 담은 프로미스를 반환합니다.
  4. Promise.resolve(value) – 주어진 값을 사용해 이행 상태의 프로미스를 만듭니다.
  5. Promise.reject(error) – 주어진 에러를 사용해 거부 상태의 프로미스를 만듭니다.

Promise.all

Pomise.all 은 실무에서 가장 많이 쓰이는 프로미스 메소드입니다.

Promise.all은 요소 전체가 프로미스인 배열을 받고 새로운 프로미스를 반환합니다. 배열 안 프로미스가 모두 처리되면 새로운 프로미스가 이행되는데, 배열 안 프로미스의 결과값을 담은 배열이 새로운 프로미스의 result가 됩니다. 만약 프로미스가 하나라도 거부되면 Promise.all은 즉시 거부되고 배열에 저장된 다른 프로미스의 결과는 완전히 무시됩니다.

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); //[3, 42, "foo"]
});

참고
실전 리액트 프로그래밍 개정판
자바스크립트 Promise 쉽게 이해하기
[JavaScript]바보들을 위한 Promise 강의-도대체 Promise는 어떻게 쓰는거야?
Promise.all()

profile
즐거움을 찾는 개발자🐯

0개의 댓글