[JavaScript] Promise 정리

정현섭·2021년 6월 29일
0
post-thumbnail

Promise란 어떠한 값을 미래에 반환하는 자바스크립트 오브젝트다.

Promise는 비동기 작업이 (성공 또는 실패로) 끝났을 때, 그 다음 수행 내용을 처리할 수 있게 하는 도구다.

그냥 콜백함수를 넘겨주는 방식으로도 할 수 있지만 Callback Hell 문제 때문에 Promise가 필요하다.

Promise의 상태는 총 3가지다

  • pending (대기)
    • 넘겨받은 콜백 함수에서 아직 resolve() 혹은 reject()가 실행되기 전 상태.
  • fulfilled (성공)
    • 넘겨받은 콜백 함수에서 resolve()가 실행된 상태.
  • rejected (실패)
    • 넘겨받은 콜백 함수에서 reject()가 실행된 상태.

기본 예시

Example 1. Promise 성공 혹은 실패 시 다음 행동 정의

const pr = new Promise((resolve, reject) => {
    setTimeout(()=>{
      	resolve('Good');
        // reject('Bad');
        
    }, 3000);
});

pr.then(
    (result) => console.log('Success!', result),
    (error) => console.log('Error!', error)
)
  • 위 처럼 Promise는 생성시 콜백함수 하나를 넘겨준다.

    • 해당 콜백함수는 (resolve, reject) 라는 두 인자를 가지는데
    • 이때 resolve, reject도 함수다.
  • Promise에 넘긴 콜백함수 내용이 메인 로직이다.

    • 해당 로직에서 결과적으로 resolve()가 실행되느냐, reject()가 실행되느냐에 따라서
    • 이후 then() 메소드의 두 콜백 함수 중 어떤 것이 실행되는지가 결정된다.
      • resolve 시 → 첫번째 콜백
      • reject 시 → 두번째 콜백
  • 근데 then() 메소드에 두 콜백함수를 같이 넣어주는 대신에

  • catch() 메소드를 사용하면 더 가독성 좋게 만들 수 있다.

  • 아래 예제코드는 위 예제코드와 동작이 같다.

const pr = new Promise((resolve, reject) => {
    setTimeout(()=>{
        resolve('Good');
        // reject('Bad');
    }, 3000);
});

pr.then(
    (result) => console.log('Success!', result)
).catch(
    (error) => console.log('Error!', error)
)
  • 마지막으로 finally() 메소드도 있는데, 여기에 인자로 넣어주는 콜백함수는
  • resolve() 됐던 reject() 됐던 pending 상태만 벗어나면 항상 실행된다.
pr.then(
  (result) => console.log('Success!', result)
).catch(
  (error) => console.log('Error!', error)
).finally(
  () => console.log('Done.');
)
  • 위와 같이 사용할 수 있고 보통 로딩화면 같은거 없앨때 유용하다.

재밌는 문제

상황

  • Task1 (2초가 걸림)
  • Task2 (0.5초가 걸림)
  • Task3 (1초가 걸림)

우선 위 Task들을 정의하고,

각 Task를 순서대로 처리한 후 console.log('Done'); 을 하는 시뮬레이션을 구현해보자.

1. Callback을 이용한 방식

const task1 = (callback) => {
    setTimeout(() => {
        console.log('Task 1 is completed!');
        callback();
    }, 2000);
};

const task2 = (callback) => {
    setTimeout(() => {
        console.log('Task 2 is completed!');
        callback();
    }, 500);
};

const task3 = (callback) => {
    setTimeout(() => {
        console.log('Task 3 is completed!');
        callback();
    }, 1000);
};

task1(() => {
    task2(() => {
        task3(() => {
            console.log('Done.');
        });
    });
})

2 - 1. Promise를 이용한 방식 (잘못 한 경우)

  • 아래 코드는 console.log 가 2, 3, 1, Done 순으로 찍힌다.

  • 이유가 뭘지 생각해보자.

const task1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('Task 1 is completed!');
        resolve();
    }, 2000);
})

const task2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('Task 2 is completed!');
        resolve();
    }, 500);
})

const task3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('Task 3 is completed!');
        resolve();
    }, 1000);
})

task1
		.then(() => task2)
    .then(() => task3)
		.then(() => console.log('Done.'));
  • Promise 오브젝트가 생성되는 동시에 callback 함수가 실행되기 때문이다!
  • 즉, 위에서 task1, 2, 3를 정의할 때 부터 각 callback의 setTimeout함수가 이미 실행된 것이다.
  • 제대로 사용하려면 각 task를 promise를 리턴하는 함수로 만들어야한다.

2 - 2. Promise를 이용한 방식 (잘 한 경우)

const task1 = () => {
    console.log('Task 1 is started.')
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('Task 1 is done.');
            resolve(1);
        }, 2000);
    })
};

const task2 = () => {
    console.log('Task 2 is started.')
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('Task 2 is done.');
            resolve(2);
        }, 500);
    })
};

const task3 = () => {
    console.log('Task 3 is started.')
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('Task 3 is done.');
            resolve(3);
        }, 1000);
    })
};

task1()
  .then(() => task2())
  .then(() => task3())
  .then(() => console.log('Done'));
  • 결과

  • 좀 더 완성도 있게 해보면 아래와 같이 할 수도 있다.

task1()
    .then(() => task2())
    .then(() => task3())
    .then(() => console.log('Completed!'))
    .catch(console.log)
    .finally(() => console.log('Done.'));
  • 결과1 (성공)

  • 결과2 (Task 2에서 reject한 경우)

위 코드처럼 프로미스가 연결, 연결 되는 것을 프로미스 체이닝 이라고 한다.

Promise.all

  • 여러 promise 함수를 병렬적으로 실행시킬 때 사용.

만약 여러 promise를 전부 실행시켜서 결과값을 동시에 가져오고 싶다면

(즉, promise 사이에 dependency가 없다면)

Promise.all 메소드를 사용해야 한다.

Promise.all([task1(), task2(), task3()])
    .then((res) => console.log(res))
    .catch(console.log)
    .finally(() => console.log('Done.'));
  • 결과

  • 1, 2, 3이 전부 실행되고 결과값을 배열로써 받아온 것을 확인할 수 있다.

  • 이때 console.time 함수를 이용해서 시간을 찍어보면

console.time("x");
Promise.all([task1(), task2(), task3()])
    .then((res) => {
        console.log(res);
        console.timeEnd("x");
    })
    .catch(console.log)
    .finally(() => console.log('Done.'));

  • Task 1, 2, 3을 처리하는데 총 2초밖에 안 걸린 것을 확인할 수 있다.
  • Task1, 2, 3을 한꺼번에 실행시키기 때문이다.

Promise.race

  • 여러 Task promise를 동시에 실행시키고 가장 먼저 완료된 결과를 가져온다.
console.time("x");
Promise.race([task1(), task2(), task3()])
    .then((res) => {
        console.log(res);
        console.timeEnd("x");
    })
    .catch(console.log)
    .finally(() => console.log('Done.'));
  • 결과

Task2 가 완료되자마자 바로 then 메소드가 실행 된 것을 확인할 수 있다.

다른 Task들은 실행이 마저 되기는 하지만 이후 결과는 Promise chaining이 안된다.

만약 가장 처음 끝나는 promise가 reject라도 똑같이 catch구문이 바로 실행된다.

Promise의 실행구조 (from MDN)

0개의 댓글