TIL [Promise & Promise.all]

Callback

다른 함수의 전달인자로 넘기는 함수이다. 장점은 비동기 프로그래밍을 할 수 있다는 것이고 단점은 콜백 지옥에 빠질 수 있다.


Promise

Promise는 말 그대로 약속이라는 뜻이다. 지금 당장은 없으니깐 이따가 주겠다는 약속으로서 향후에 값을 생산해 내는 객체이다. 값을 얻어서 값을 보내주거나 얻지 못한다면 그 이유와 함께 에러를 보내준다.

Promise는 콜백의 단점(콜백 지옥)을 해결하기 위해 만들어졌다. 그렇다고 해서 promise가 콜백을 대체하는 것은 아니다. 사실 promise에서도 콜백은 사용되기 때문이다. Promise는 단지 콜백이 예측 가능한 패턴으로 사용할 수 있게 하며, promise 없이 콜백만 사용했을 때 나타날 수 있는 버그를 상당수 해결한다.


Promise에서 resolve와 reject 함수의 의미

1. resolve 함수

const getData = function(){
  return new Promise((resolve, reject) => {
    let data = 100;
    resolve(data); // resolve 함수에 data를 인자로 넣는다.
  })
}

getData(); // 향후에 값을 생산해 내는 promise 객체를 생성함 
getData().then(data => { // resolve 함수에 인자로 들어간 data가 여기서 나온다.
  console.log(data); // 100;
});
        

resolve -> then을 통해서 받는다.
resolve 함수는 성공한 비동기 요청을 받아서 반환한다. 위의 예시 같은 경우엔 resolve 함수에 data를 넣어줬다. resolve 함수에 인자로 넣어준 값은 .then()을 통해서 다음 함수가 실행될 때 그 함수의 인자로 들어간다.

2. reject 함수

const getData = function(){
  return new Promise((resolve, reject) => {
    let data = 100;
    reject(new Error('Request has failed.')); // resolve 함수에 data를 인자로 넣는다.
  })
}

getData()
.then()
.catch( err => { // reject 함수에 인자로 들어간 new Error('Request has failed.')가 여기서 나온다.
  console.log(err); // Error: Request has failed.
});

reject -> catch를 통해서 받는다.
reject 함수는 실패한 비동기 에러를 받아서 반환한다. reject 함수에 인자로 넣어준 값은 .then().catch()를 통해 promise chain의 맨 마지막에 적히게 된다. 또한 reject 함수에 인자는 catch() 안에 들어갈 함수의 인자로 들어가게 된다.


Promise의 상태

1. Pending (대기)

const promiseTest = function(condition){
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if(condition) {
        resolve('Success!');
      } else{
        reject('Failed!');
      }
    }, 1000);
  });
};

promiseTest라는 함수를 만들었다. 이 함수는 condition을 파라미터로 받고 new Promise를 통해 promise 객체를 반환한다.

const promiseTest = function(condition){
  return new Promise((resolve, reject) => {
    // 비동기 작업을 보여주기 위해 setTimeout을 이용
    setTimeout(() => {
      if(condition) {
        resolve('Success!');
      } else{
        reject('Failed!');
      }
    }, 1000);
  });
};

promiseTest(condition); // pending 상태

promiseTest를 실행했고 당연히 함수의 실행 결과는 새로운 promise 객체이다. 아직 resolve나 reject를 하지 않은 이 시점을 pending(대기) 상태라고 할 수 있다. 이후 비동기 작업을 통해 결과물이 약속한대로 잘 준비가 되면 resolve 함수를 호출, 실패한다면 reject 함수가 호출되는 것이다.

2. Fulfilled (이행)

const promiseTest = function(condition){
  return new Promise((resolve, reject) => {
    // 비동기 작업을 보여주기 위해 setTimeout을 이용
    setTimeout(() => {
      if(condition) {
        resolve('Success!');
      } else{
        reject('Failed!');
      }
    }, 1000);
  });
};

promiseTest(true)
.then((data) => {
  console.log(data); // Success!
})

promiseTest 함수는 새로운 promise 객체를 만들었고 인자로 true를 넣었기 때문에 promise 객체가 정상적으로 작동했다고 할 수 있다. 성공한 비동기 요청(이행 상태)은 then()을 통해 호출할 수 있다. 위의 예시 처럼 then()을 이용하여 처리 결과 값을 받을 수 있다.

3. Rejected (실패)

const promiseTest = function(condition){
  return new Promise((resolve, reject) => {
    // 비동기 작업을 보여주기 위해 setTimeout을 이용
    setTimeout(() => {
      if(condition) {
        resolve('Success!');
      } else{
        reject('Failed!');
      }
    }, 1000);
  });
};

promiseTest(false)
.then((data) => {
  console.log(data); 
})
.catch((data) => {
  console.log(data); // Failed!
})

비동기 요청이 실패했을 경우 catch()를 통해 reject 함수에 넣은 인자를 받을 수 있다는 것을 보여주기 일부러 만들어 본 예제이다. promiseTest 함수를 실행해서 새로운 promise 객체를 만들었고 일부러 false를 인자로 넣었다. 약속한대로 값이 생성되지 않는다면(조건이 실패) reject 함수에 인자로 들어간 값이 .catch()를 통해 콘솔 창에 나온다.


new Promise()를 통해 생성된 Promise의 인스턴스

const getData = function(){
  return new Promise((resolve, reject) => {
    let data = 100;
    resolve(data); // resolve 함수에 data를 인자로 넣는다.
  })
}

let temp = getData();

new Promise()를 통해 만들어진 인스턴스를 만들었더니 안에 여러 메소드가 있는것 을 확인할 수 있다.

1. then 메소드

then()은 Promise를 리턴하고 두 개의 콜백 함수를 인자로 받는다. 하나는 Promise가 이행했을때, 다른 하나는 거부했을때를 위한 콜백 함수이다.

const promise = function(condition){
    return new Promise((resolve, reject) => {
        if(condition){
            resolve('Success!');
        } else{
            reject('Failed!');
        }
    }
    )
}     

// 성공했을 경우
promise(true).then((value) => {
    console.log(value); // Success!
}, (reason) => {
    console.log(reason);
})

// 실패했을 경우
promise(false).then((value) => {
    console.log(value); 
}, (reason) => {
    console.log(reason); // Failed!
})

then()을 이용하면 에러를 잡아낼 수 있지만 가급적이면 catch()를 사용하는 것이 좋다.


실무에서 쓸 만한 Promise 예제

var userInfo = {
  id: '[email protected]',
  pw: '1234'
};

function parseValue(id, pw) {
  return new Promise((resolve, reject) => {
     if (id === '[email protected]' && pw === '1234'){
        resolve(true);
     } else{
        reject('Login Failed One!');
     }
  });
}

function display(isCorrect) {
  return new Promise((resolve, reject) =>{
      if(isCorrect){
        resolve('Login Success!');
      } 
  });
}

// 로그인에 성공시
parseValue(userInfo.id, userInfo.pw)
.then(data => {
    return display(data); // true를 display 함수의 인자로 보낸다.
})
.then(data => {
    console.log(data); // Login Success!
})

// 로그인에 실패시
parseValue('unknown_id', 'unknown_pw')
.then(data => { 
    return display(data);
})
.then(data => {
    console.log(data);
})
.catch(data => {
    console.log(data); // Login Failed One!
})

만약 유저가 아이디와 비밀번호를 입력했을때 로그인에 성공했을 때와 실패했을 때를 보여주는 예제이다. Promise chaining을 통해 로그인에 성공했을 경우 콘솔창에 성공했다는 메시지를, 실패했을 경우 콘솔창에 실패했다는 메시지를 전달한다.


Promise.all

여러 개의 promise를 동시에 실행시키고 모든 promise가 준비되면 실행되는 메서드이다. 복수의 URL에 동시에 요청을 보내고, 요청에 대한 응답이 모두 완료된 후에 콘텐츠를 처리할 때 이런 상황이 발생한다. Promise.all은 요소 전체가 promise인 배열을 받고 새로운 promise를 반환한다.

Promise.all([
  new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
  new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
  new Promise(resolve => setTimeout(() => resolve(3), 1000))  // 3
])

.then(values => {
    console.log(values); // [1, 2, 3]
}) 

Promise.all을 실행시키면 인자로 들어간 모든 promise가 실행이 된다. 위의 예시 같은 경우엔 3000ms(3초)가 지나야지만 모든 promise가 끝이난다. 만약 이중에 한 promise라도 이행하지 못하면 Promise.all이 반환하는 promise는 에러와 함께 거부된다. then()은 배열을 반환하는데 배열의 요소는 각 promise에서 이행한 뒤 나온 결과값이다.

let urls = [
  'https://api.github.com/users/iliakan',
  'https://api.github.com/users/remy',
  'https://api.github.com/users/jeresig'
];

// fetch를 사용해 url을 프라미스로 매핑합니다.
let requests = urls.map(url => fetch(url)); 


// Promise.all은 모든 작업이 이행될 때까지 기다립니다.
Promise.all(requests)
  .then(responses => responses.forEach(
    response => console.log(`${response.url}: ${response.status}`)
  ));

// https://api.github.com/users/iliakan: 200
// https://api.github.com/users/remy: 200
// https://api.github.com/users/jeresig: 200

또한 Promise.all은 대게 promise가 요소인 배열을 받는데 promise가 아닌 객체가 배열을 구성하면, 요소가 그대로 결과 배열로 전달된다.

Promise.all([
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(1), 1000)
  }),
  2,
  3])
  .then(values => console.log(values)); // [1, 2, 3]

여기서 Promise.all가 받은 배열의 첫 번째 요소를 제외한 두 번째와 세 번째 요소는 숫자이다. 따라서 요소가 그대로 배열에 전달된 것을 알 수 있다.

좋은 웹페이지 즐겨찾기