본문 바로가기

언어/Javascript

[JavaScript] Promise 란

Promise란?

비동기 처리에 사용되는 자바스크립트 객체, 비동기 작업이 맞이할 성공 혹은 실패를 나타냄

 

프로미스는 하나의 상자라고 볼 수 있음. 프로미스라는 상자는 비동기 작업이 시작될 때 만들어짐. 처음엔 상자가 텅 비어있다가 언젠가 결과물로 채워지게 됨. (비동기 작업이 완료될 때)

 

 

 

 

비동기 작업은 완료되기까지 시간이 좀 걸린다는 특징.

비동기 작업이 진행 중일 때는 프로미스 상자가 텅 비어있다. 그러다가 비동기 작업이 끝나면, 그 결과물로 프로미스 상자가 채워지게 됨. 이처럼 프로미스는 비동기 작업의 상태를 나타냄

 

진행 중인 비동기 작업은 언젠가 끝나게 됨. 작업이 성공할 수도 있고, 실패할 수도 있음. 예를 들어 서버로부터 데이터를 받아오는 비동기 작업이라고 가정했을 때, 데이터를 성공적으로 받아오면 그 작업은 성공한 것. 중간에 네트워크 에러가 나서 데이터를 받아오지 못하면 작업이 실패한 것이다.

 

비동기 작업이 실행 중일 때는 프로미스 상자가 비어있음. 프로미스 상자에는 딱지가 하나 붙어 있는데, 프로미스 상자의 현재 상태가 적혀 있음.

- 비동기 작업이 진행 중일 때 : 대기

시간이 지나고 진행 중이던 작업이 끝나면 성공, 실패 둘 중 하나로 결정된다.

- 비동기 작업이 성공적으로 마무리됨 : 성공

 

그리고 상자 안에는 비동기 작업의 결과값이 들어가게 됨.

- 비동기 작업이 실패함 : 실패

 

상자 안에는 작업 중에 발생한 에러가 들어가게 됨.


[Promise의 3가지 상태]

1. Pending(대기)
2. Fulfilled(성공)
3. Rejected(실패)

 

이 세 가지는 각각 비동기 작업의 대기, 성공, 실패 상태를 의미.

 

[pending - 대기]

비동기 작업이 진행 중일 때 Promise 객체는 Pending state를 가지고 있음. 작업이 성공하지도 실패하지도 않은 대기 상태. 이때, promise의 result는 'undefined'.

 

 

[fulfilled - 성공]

비동기 작업이 성공적으로 완료되면 Promise 객체는 fulfilled state를 갖게 됨. 이때 result는 결과값으로 채워지게 되는데, 비동기 작업이 반환한 결과값이 됨. 예를 들어 서버에서 데이터를 받아오는 작업이라면 받아온 데이터가 Promise의 result가 되는 것

 

 

 

[rejected - 실패]

비동기 작업이 진행되다가 문제가 발생해 실패하게 되면 Promise 객체는 rejected state를 갖게 됨. 비동기 작업이 실패하면 Promise의 result로는 에러 객체가 들어가게 됨. 에러를 보고 비동기 작업이 왜 실패한 것인지 알 수 있음.

 

 

이처럼 Promise 객체는 항상 pending state에서 시작해서 비동기 작업의 결과에 따라 fulfilled state 또는 rejected state가 된다. 현재 Promise 객체가 가지는 state에 따라 result 또한 달라지게 된다.

 

 


 

Promise 객체는 'new' 키워드를 사용해서 만들 수 있음.
Promise의 생성자는 함수를 하나 전달 받는데, 그 함수를 'executor 함수'라고 함. 이 함수 안에 내가 원하는 비동기 작업 코드를 작성하면 됨. 익시큐터 함수는 두 가지 인자를 전달 받음. resolve, reject!

const promise = new Promise((resolve, reject) => {
  console.log('비동기 작업')
})

 

 

여기서 알 수 있는 점은, new 키워드를 사용해서 promise 객체가 생성되는 그 즉시 익시큐터 함수가 실행된다는 것. 그렇다면 익시큐터 함수가 전달 받는 resolve, reject는 뭘까? 이들은 익시큐터 함수 안에서 호출해줄 수 있는 함수임. 내가 하고자 하는 비동기 작업이 성공하면 resolve, 실패하면 reject를 호출.

 

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const data = { name : '코딩'}
    console.log('네트워크 요청 성공!')
  }, 1000)
})

console.log(promise)

 

 

promise의 state는 pending. result는 undefined. 대기라는 딱지가 붙어 있는 텅 빈 상자임. 내가 아직 프로미스한테 비동기 작업이 완료되었다는 것을 알려주지 않았기 때문에 아직 대기 상태. 1초가 지나고 데이터를 받아오는 것이 완료되면 프로미스한테 작업이 완료되었다고 알려주어야 함. 인자로 전달받은 resolve를 사용하면 됨. 익시큐터 함수에 인자로 들어오는 resolve는 비동기 작업이 완료됐다 라고 말해주는 함수임. 1초가 지난 다음 데이터를 성공적으로 받아오면, resolve 함수를 호출하면 됨. resolve의 인자로는 비동기 작업의 결과물을 넣어주면 됨. 위 코드의 경우, result는 'data'가 될 것임. 이제 resolve가 된 이후 프로미스가 어떻게 생겼는지 확인할 것임. 프로미스는 1초가 지난 후 resolve가 되니까 2초 뒤에 프로미스가 어떻게 생겼는지 확인하면 됨. setTimeout을 한 번 더 사용하기

 

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const data = { name : '코딩'}
    console.log('네트워크 요청 성공!')
    resolve(data)
  }, 1000)
})

setTimeout(() => {
  console.log(promise)
}, 2000)

 

 

열어보면 프로미스의 state는 성공을 나타내는 fulfilled. result는 내가 넣어준 객체가 됨. 성공 딱지가 붙은 상자 안에 비동기 작업의 결과물인 데이터가 들어가 있는 것.

 

이번엔 비동기 작업이 실패하는 경우를 생각해보자. 만약 서버에서 데이터를 가져오다가 문제가 생겨 데이터가 null이 되었다고 가정. 만약 데이터가 성공적으로 받아와져서 데이터가 null이 아니라면 resolve를 if문 안에서 호출해주고, 데이터가 null이라면 else문 코드를 실행하면 됨. 이제 비동기 작업이 실패했다는 것을 프로미스에게 알려주어야 함. 그럴 때 익시큐터 함수에 전달된 두 번째 인자인 reject를 호출하면 됨. reject의 인자는 비동기 작업이 왜 실패했는지를 알려주는 에러 객체를 넣어주면 됨. 이제 비동기 작업이 실패했을 때 프로미스가 어떻게 생겼는지 확인해보자.

 

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const data = null
    if(data){
      console.log('네트워크 요청 성공!')
    }else{
      reject(new Error('네트워크 문제로 fail'))
    }
    resolve(data)
  }, 1000)
})

setTimeout(() => {
  console.log(promise) // Uncaught (in promise) Error: 네트워크 문제로 fail
}, 2000)

 

promise의 state는 rejected. result는 넣어준 에러 객체가 들어감. 실패 딱지가 붙은 상자에 에러가 들어가 있음.

 


Promise를 반환하는 비동기 함수 사용하기

지금까지는 비동기 함수를 구현하는 입장에서 프로미스를 사용했음.
그럼 이제 프로미스를 리턴하는 비동기 함수를 사용하는 입장이 되어보자.
비동기 함수를 의미 있게 사용하기 위해서는 비동기 작업이 다 끝날 때까지 기다릴 수 있어야 함. 만약, 비동기 작업이 성공적으로 완료되었다면 그에 따른 처리를 할 수 있어야 함. 또한 실패했다면 적절한 에러 처리를 할 수 있어야 함. 이 모든 것들을 비동기 함수로부터 전달받은 promise 객체를 사용해서 간편하게 할 수 있음.

 

promise 객체는 then, catch, finally라는 API를 제공해줌. 이것들을 사용해서 비동기 작업에 대한 후처리를 간편하게 할 수 있음.

 

비동기 작업이 성공적으로 끝나면, promise는 fulfilled state가 됨.

 

 

 

[then]

result 안에 들어가 있는 데이터를 받아서 사용할 수 있어야 함. 내가 전달 받은 프로미스 객체는 'then'이라는 메서드를 가지고 있음. 이 메서드는 콜백 함수를 전달 받음. then 안에다가 비동기 작업의 후처리를 해주면 됨.

 

  function getData() {
    const promise = new Promise((resolve, reject) => {
      setTimeout(() => {
        const data = null;
        if (data) {
          console.log("네트워크 요청 성공!");
        } else {
          reject(new Error("네트워크 문제로 fail"));
        }
        resolve(data);
      }, 1000);
    });

    return promise;
  }

  const promise = getData();
  promise.then(() => {
    console.log("완료");
  });

 

비동기 작업이 완료되고 나서 then 안에 있는 콜백 함수가 실행됨. then은 비동기 작업이 완료될 때까지 기다렸다가 fulfilled 상태로 바뀌면, then 안에다 전달해준 콜백 함수를 호출해줌. 콜백 함수는 매개 변수 하나를 전달 받는데, 프로미스의 result.

 

 

  function getData() {
    const promise = new Promise((resolve, reject) => {
      setTimeout(() => {
        const data = { name: "코딩" };
        if (data) {
          console.log("네트워크 요청 성공!");
        } else {
          reject(new Error("네트워크 문제로 fail"));
        }
        resolve(data);
      }, 1000);
    });

    return promise;
  }

  const promise = getData();

  promise.then((data) => {
    console.log(data); // { name : '코딩' }
  });

 

 

[catch]

이번엔 비동기 처리 도중에 문제가 생겨 프로미스가 실패하는 경우를 생각해보자. promise의 state는 rejected가 되고, result는 에러 객체.

  function getData() {
    const promise = new Promise((resolve, reject) => {
      setTimeout(() => {
        const data = { name: "코딩" };
        if (data) {
          console.log("네트워크 요청 성공!");
        } else {
          reject(new Error("네트워크 문제로 fail"));
        }
        resolve(data);
      }, 1000);
    });

    return promise;
  }

  const promise = getData();

  promise.then((data) => {
    console.log(data); 
  }).catch((error) => {
    console.log(error) // 네트워크 문제로 fail
  })

 

 

[finally]

프로미스가 성공하든 실패하든 상관 없이 무조건 수행되어야 하는 코드가 있을 때는 finally를 사용

안에 콜백 함수를 전달해주면, 콜백 함수는 프로미스의 성공 여부와는 관계 없이 제일 마지막에 호출

비동기 작업의 성공/실패 여부와는 관계 없이 반드시 실행되어야 하는 코드가 있다면 finally 안에 넣으면 됨

 

  function getData() {
    const promise = new Promise((resolve, reject) => {
      setTimeout(() => {
        const data = { name: "코딩" };
        if (data) {
          console.log("네트워크 요청 성공!");
        } else {
          reject(new Error("네트워크 문제로 fail"));
        }
        resolve(data);
      }, 1000);
    });

    return promise;
  }

  const promise = getData();

  promise
    .then((data) => {
      console.log(data);
    })
    .catch((error) => {
      console.log(error); // 네트워크 문제로 fail
    })
    .finally(() => {
      console.log("마무리 작업");
    });