비동기 처리 - Promise

Seokjun Moon·2023년 3월 2일
0

javascript

목록 보기
2/2

2023.03.01

앞서 공부했던 비동기 처리를 할 때 많이 사용되는 자바스크립트 객체라고 합니다.




왜 Promise를 사용할까

주로 서버에서 받아온 데이터를 화면에 표시할 때 사용합니다. 앞서 보았던 $.get() 과 같은 함수로 서버에 데이터를 요청하였는데, 아직 데이터를 받지 않은 상태에서 화면에 데이터를 표시하려고 하면 오류가 발생하거나 빈 화면이 나타나게 됩니다. 비동기 처리로 인해 데이터를 받기 전에 다음 구문을 실행하기 때문입니다. 따라서 이런 문제점을 해결하기 위한 방법 중 하나가 Promise 입니다.

Promise 를 사용하면 다음과 같은 장점이 있습니다.

  • 비동기 처리 시점을 명확하게 표현할 수 있다.
  • 연속된 비동기 처리 작업을 수정, 삭제, 추가하기 편하고 유연하다.
  • 비동기 작업 상태를 쉽게 확인할 수 있다.
  • 코드의 유지 보수성이 증가한다.



Promise 코드 - 기초

ajax로 작성한 간단한 통신 코드를 보면

const getData = (callbackFunc) => {
    $.get('url', (response) => {
        callbackFunc(response);
    });
};
getData((dat) => {
    console.log(dat);
});

이 경우 비동기 처리를 위해 콜백 함수를 사용하였습니다. 위 코드를 Promise 로 나타내면 다음과 같습니다.

const getData = (callback) => {
    return new Promise((resolve, reject) => {
        $.get('url', (response) => {
            resolve(response);
        });
    });
};
getData().then((dat) => {
    console.log(dat);
});

콜백 함수와 달리, resolve(), reject(), then() 과 같은 함수를 사용하는 구조로 바뀌었습니다. 이를 이해하기 위해서는 Promise의 3가지 상태에 대해 알아야 했습니다. 3가지 상태에 따라 비동기 작업을 실행, 그 다음 후속 처리 메소드인 then(), catch() 를 통해 비동기 처리 결과 메시지를 전달받아 처리합니다.




Promise의 3가지 상태 (states)

여기서 말하는 상태는 Promise의 처리 과정을 의미합니다. 상태에는 3가지가 존재하는데

  • Pending(대기) : 비동기 처리를 진행 중인 상태
  • Fullfilled(이행) : 비동기 처리가 완료되어 Promise가 결과값을 반환한 상태
  • Rejected(실패) : 비동기 처리가 실패하거나 오류가 발생한 상태

Pending(대기)

Promise로 등록한 비동기 작업이 완료되기까지 대기 중인 상태입니다. new Promise() 메서드를 호출하면 Pending 상태가 됩니다. 그러니까 생성된 직후부터 resolve() 혹은 reject() 가 호출되기 전까지의 순간을 Pending 상태라고 할 수 있습니다.

new Promise();

위 메소드를 호출할 때 콜백 함수를 선언할 수 있고, 파라미터로는 resolve, reject 가 있습니다.

new Promise((resolve, reject) => {
    ...
});

Fullfilled(이행)

콜백 함수의 파라미터인 resolve() 를 실행하면 Fullfilled(이행) 상태가 됩니다. 이러한 상태가 되면, then() 을 이용하여 비동기 처리 결과를 전달받을 수 있습니다.

new Promise((resolve, reject) => {
    resolve();
});

위와 같이 resolve() 가 실행되면 .then() 으로 등록된 첫번째 함수를 실행합니다. 비슷하게 reject() 가 실행되면 .then() 으로 등록된 두번째 함수를 실행합니다. .catch() 로 등록된 함수는 비동기 처리 혹은 .then() 메소드 실행 중 발생한 에러(예외)가 발생하면 호출됩니다.

const getData = () => {
    return new Promise((resolve, reject) => {
        var value = 100;
        resolve(value);
    });
};
getData().then((resolvedData) => {
    console.log(resolvedData);
});

위 코드의 경우 value = 100 이라는 구문이 오류없이 실행되고 그 값이 resolve() 함수를 통하여 .then() 에 등록된 첫번째 함수로 전달됩니다. 그 결과 100이라는 값이 출력됩니다.


Rejected(실패)

reject() 가 호출되면, Rejected(실패) 상태가 됩니다. 이 상태가 되면 .then() 으로 등록된 두번째 함수 혹은 .catch() 로 등록된 함수를 통해 실패 처리의 결과를 받을 수 있습니다.

두가지의 경로로 에러를 받을 수 있지만, 미묘한 차이가 있습니다. .then() 의 두번째 함수는 reject() 호출 시에만 작동하는 반면, .catch() 로 등록된 함수는 .then() 에서 호출한 함수에서 오류가 났을 경우에도 호출됩니다. 따라서 비동기 처리의 전체적인 오류를 다루기 위해서는 .catch() 함수가 더 적절합니다.


예제

const getData = () => {
    return new Promise((resolve, reject) => {
        $.get('/asdf', (response) => {
            if (response) resolve(response);
            reject(new Error("failed"));
        });
    });
};
getData().then((dat) => {
    console.log(dat);
}).catch((err) => {
    console.error(err);
});

위 코드는 '/asdf' 로 요청하여 응답을 받으면, 그 결과를 콘솔 창에 출력하고 에러가 뜨면 콘솔 창에 에러를 보여줍니다.




Promise Chaining

Promise는 여러개를 연결할 수 있습니다. .then() 메소드를 호출하면 새로운 Promise 객체가 반환됩니다. 따라서 체이닝이 가능하고, 이와 연결된 작업을 콜백 지옥을 피하면서 작성할 수 있게 됩니다. 예를 들어

new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1);
    }, 3000);
})
.then((data) => {
    console.log(data);
    return data + 10;
})
.then((data) => {
    console.log(data);
    reutrn data + 10;
})
.then((data) => {
    console.log(data);
});

위 코드를 실행하게 되면

1
11
21

이 출력됩니다. 이 외에도 실제 웹 서비스를 생각해보면, 유저 정보를 받아 이를 검사하고 화면에 표시하는 로그인 로직의 경우를 생각해 보았습니다. 이 경우에는

getData(info)
    .then(parse)
    .then(auth)
    .then(display)

와 같은 구조로 콜백 지옥을 피하면서 가독성과 유지보수에 유리한 로직을 짤 수 있습니다. 이렇게 체이닝을 통한 처리 시에, 에러 핸들링의 경우 .then() 이 연결되는 도중 에러가 발생하면 .catch() 로 이동하며 그 다음 이어지는 .then() 은 실행되지 않습니다.




그 외 메소드들

그 외에 Promise 에 있는 메소드들 입니다. all(), race(), allSettled() 가 있습니다.


Promise.all([])

여러 Promise들을 한번에 병렬 처리를 할 수 있습니다. 가장 마지막으로 끝나는 Promise를 기준으로 수행되고, 모든 Promise들이 fullfilled 상태가 되면 결과값을 배열에 담아 새로운 Promise를 반환합니다. 만약 Promise들 중 하나라도 에러(rejected)가 발생하면 rejected 상태가 되고 수행을 종료합니다.


Promise.race([])

all() 메소드와는 다르게 병렬로 처리하지 않고 가장 먼저 끝나는 Promise의 결과값을 resolve() 로 반환합니다.


Promise.allSettled([])

all() 메소드와 비슷하지만, 이 메소드는 에러가 발생해도 나머지 모든 Promise를 실행하여 결과적으로 수행된 상태와 결과값을 담은 배열을 담아 resolve() 로 반환합니다.

또다른 차이점은 반환된 배열에는

[
    { status: 'fullfilled', value: 1 },
    { status: 'rejected', reason: 2 }
]

와 같은 결과 배열을 반환하는데, fullfilled의 경우 value, rejected의 경우 reason을 프로퍼티로 갖습니다.




출처

profile
차근차근 천천히

0개의 댓글