티스토리 뷰

이 내용은 아래 링크에서 동영상 강의도 같이 보실 수 있습니다.

[ 동영상 강의 보기 ]

Promise

자바스크립트는 비동기 처리 결과를 가져오거나 에러처리를 하기 위해 콜백함수를 사용했습니다.
대표적으로 jquery가 제공하는 $.ajax 함수를 많이 사용하는데 ajax함수의 결과를 가지고 다음 ajax함수를 호출해야 하는 경우가 연달아 생긴다면 가독성이 매우 떨어지고 에러처리도 쉽지 않습니다.

먼저 아래 현기증 나는 코드를 한번 보겠습니다.
// 현재 로그인한 사용자 정보 가져옴
$.ajax({
    url: '/api/current-user',
    success: function(user) {
        const userId = user.id;
        
        // 넘겨받은 user_id를 기반으로 친구 목록 가져옴
        $.ajax({
            url: '/api/friend-list', 
            data: {
                user_id: userId
            },
            success: function(friends) {
                let i;

                for (i = 0; i < friends.length; i ++) {
                    const friendId = friends[i].id;

                    // 넘겨받은 user_id를 기반으로 최근 구매항목 가져옴
                    $.ajax({
                        url: '/api/latest-purchase-item', 
                        data: {
                            user_id: friendId
                        },
                        success: function(item) {
                            console.log(item.name);
                        }
                    })
                }
            }
        })
    }
});

확실한 비교를 위해 극적인 예제 코드를 가져오긴 했지만 ajax중첩이 2번정도는 종종 사용하기도 하고 일단 1번만 사용하는것도 가독성이 매우 낮아집니다.
이런 기존 콜백을 사용한 비동기 처리는 읽기가 매우 힘들고 에러처리가 난감한 경우가 많이 있어 에러처리를 슬쩍 제외하고 개발하는 경우도 종종 볼 수 있습니다.
이런 문제를 해결하고자 ECMA Script 2015에 promise가 추가됐는데, promise를 사용한다면 코드가 아래와같이 아주 간단해집니다.
const user = yield axios.get('/api/current-user');
const friends = yield axios.get('/api/friend-list', {
    user_id: user.id
});
let i;
for (i = 0; i < friends.length; i ++) {
    const item = yield axios.get('/api/latest-purchase-item', {
        user_id: friends[i].id
    });

    console.log(item.name);
}
위 코드는 promise와 다음에 알아볼 generator를 같이 사용해서 적용한 코드입니다.
일단 지금은 기존 콜백패턴을 사용했기 때문에 뎁스가 계속 증가하고 에러처리가 쉽지 않았던 코드가 이렇게 간단해졌다는 정도로 넘어가고  다음에 generator를 알아본 뒤, promise와 generator를 사용해서 위와같이 비동기처리를 동기처리처럼 개발하는 방법에대해 알아보겠습니다.

자, 그럼 promise를 어떻게 사용하는지 알아보겠습니다.

Promise 사용하기

promise 기본 사용방법은 아래와 같습니다.
const promise = new Promise((resolve, reject) => {
    // 비동기로 실행할 코드

    if (true) {
        // 정상적으로 처리 완료시 resolve 호출
        resolve('처리결과');
    } else {
        // 비정상 종료일경우 reject 호출
        reject('failed');
    }
});

promise.then(result => {
    console.log(result); // "처리결과" 출력
}).catch(error => {
    console.error('오류', error);
});

- Promise 객체 생성하기

비동기로 실행할 코드를 담은 함수를 Promise의 생성자로 넘겨 객체를 생성하면 넘겨받은 함수를 비동기로 즉시 처리합니다. 
개발자가 작성한 함수는 2개의 함수를 인자로 받는데, 첫번째 인자로 넘겨받는 함수는 처리 성공시 호출하고 두번째 인자로 넘겨받는 함수는 처리 실패시 호출합니다. 그래서 보통 resolve와 reject라는 이름으로 넘겨받습니다.

- 처리 결과 사용하기

생성한 Promise객체를 통해 처리 결과에 접근할 수 있습니다.
만약 처리가 모두 정상적으로 마쳐 resolve가 호출했다면 then메소드로, exception이 발생했거나 reject 함수가 호출됐다면 catch메소드를 사용해서 처리 결과에 접근할 수 있습니다.
이 두 매소드는 비동기 처리완료 시점을 고민할 필요 없습니다.
만약 처리중이라면 끝난 후에 호출되고, 처리가 끝난 뒤에 then 메소드를 연결했더라도 동일하게 호출됩니다.
then이나 catch의 인자값으로 넣는 함수의 첫번째 파라메터로는 resolve나 reject에 넘긴 값이나 이 전에 수행한 then이나 catch에서 리턴한 값이 들어옵니다.

- chaining

then과 catch는 Promise객체를 리턴하기 때문에 chaining을 하여 다양한 처리를 할 수 있습니다.
만약 then이나 catch에서 새로운 Promise객체를 리턴했다면 역시 비동기 처리를 끝낸 뒤 똑같이 then이나 catch가 호출되고, 그 외의 것을 리턴했다면 해당 리턴값을 결과로 하는 Promise를 연결해줍니다.
promise.then(result => {
    console.log(result); // "처리결과" 출력
    return '2번째 처리결과';
}).then(result => {
    console.log(result); // "2번째 처리결과" 출력
}).catch((error, a, b) => {
    console.error('오류', error);
});

Promise.all

여러 작업을 모두 비동기로 처리한 뒤 한번에 결과를 확인하고 싶을 경우가 있는데 그럴때 Promise.all을 사용하면 됩니다.

const promise1 = new Promise((resolve, reject) => {
    resolve('promise1 처리완료');
});

const promise2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('promise2 처리완료');
    }, 1000);
});

Promise.all([promise1, promise2]).then(results => {
    console.log(results);
});

Promise.all은 promise1과 promise2가 모두 처리가 완료될때까지 대기한 뒤 then에 각 promise들이 resolve한 결과 배열을 리턴합니다.

따라서 Promise.all 코드에 연결된 then은 약 1초뒤에 호출되며 results에는 ["promise1 처리완료", "promise2 처리완료"] 값이 넘어옵니다.

Promise를 사용한 예제코드

처음에 예로 들었던 복잡한 코드를 Promise만 사용해서 개선해보면 아래와 같습니다.

axios.get('/api/current-user').then(result => {
    return axios.get('/api/friend-list', {user_id: result.data.id});
}).then(result => {
    let i;
    const friends = result.data;
    let queries = [];

    for (i = 0; i < friends.length; i ++) {
        queries.push(axios.get('/api/latest-purchase-item', {user_id: friends[i].id}));
    }
    return Promise.all(queries);
}).then(results => {
    console.log(results); // 각 쿼리에 대한 결과값 array
});

axios는 $.ajax와 동일한 기능을 하지만 리턴값으로 Promise를 리턴해주는 라이브러리입니다.

Promise를 사용하니 뎁스가 더이상 깊어지지 않아 한결 보기가 더 편해졌습니다.

그리고 만약 에러처리를 하려면 간단하게 catch만 더 붙여서 작업하면 됩니다.

댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/03   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함