본문으로 건너뛰기

Promise 의 적용 시나리오

무료2015-11-21#JS#promise的用例#javascript promise use case#Promise怎么用#promise有什么用#promise实例#任务队列#任务管道

Promise 는 무슨 용도가 있나요? 어떤 시나리오에 적합한가요?

일. 직렬 태스크 큐 구현

태스크 큐란 A 가 완료되면 B 를 호출하고, B 가 완료되면 C 를 호출하는 것을 말합니다 (여기서 ABC 는 모두 비동기 작업을 나타냅니다)... 이전에는 콜백을 중첩하는 방식이었으며, 코드는 다음과 같았습니다:

taskA(data1, function(res) {
    if (res.state === OK) {
        taskB(data2, function(res) {
            if (res.state === OK) {
                taskC(data3, function(res) {
                    if (res.state === OK) {
                        // ...
                    }
                    else {
                        // err3
                    }
                });
            }
            else {
                // err2
            }
        });
    }
    else {
        // err1
    }
});

각 단계마다 오류가 발생할 수 있으며 (err1, err2, err3...), 오류 처리도 다를 수 있습니다. 코드 구조는 매우 비대해지고 (제어 불가능하게 "수평으로 발전"하여 결국 "한 덩어리"의 코드가 됨), 매우 지저분해집니다. 각 단계가 비동기 + 콜백이기 때문에 각 callback 을 분리하기가 매우 어렵습니다.

Promise 의 가장 두드러진 효과는 이러한 "콜백 피라미드"를 제거한다는 것이며, 코드는 더 이상 수평으로 발전하지 않습니다:

// taskX 는 모두 Promise 객체를 반환
taskA(data1).then(
    taskB(data2),
    errHandler1
).then(
    taskC(data3),
    errHandler2
).then(
    displayData,
    errHandler3
);

taskA 와 taskB 의 오류 처리가 동일하다면 더 간단하게 작성할 수 있습니다:

// taskX 는 모두 Promise 객체를 반환
taskA(data1).then(taskB(data2)).then(
    taskC(data3),
    errHandler12
).then(
    displayData,
    errHandler3
);

오류를 통합해서 처리하고 싶다면 이렇게 할 수 있습니다:

// taskX 는 모두 Promise 객체를 반환
taskA(data1).then(taskB(data2)).then(taskC(data3)).then(displayData).catch(errHandler);
// 다음과 동일
taskA(data1).then(taskB(data2)).then(taskC(data3)).then(displayData, errHandler);

체인 호출 + 오류 자동 뒤로 던지기로 인해 작성하기도 좋고 읽기도 좋습니다.

이. 직렬 태스크 파이프라인 구현

태스크 파이프라인이란 현재 작업의 출력을 다음 작업의 입력으로 사용할 수 있어 데이터 파이프라인을 형성하는 것을 말합니다. jQuery 코드는 다음과 같습니다:

$.post(url1, data, function(res) {
    if (res.state === OK) {
        $.post(url2, res.data, function(res) {
            if (res.state === OK) {
                $.post(url3, res.data, function(res) {
                    if (res.state === OK) {
                        // ...
                    }
                    else {
                        // err3
                    }
                });
            }
            else {
                // err2
            }
        });
    }
    else {
        // err1
    }
});

실제 시나리오에서 매우 흔합니다. 예를 들어 url1 에서 매개변수 userId 를 가져온 후, 이를拿到하여 url2 에서 서드파티 openId 를 교환하고, 마지막으로 url3 에서 orderList 를 교환한 후 결과를 사용자에게 표시합니다. 이러한 로직은 모두 자연스러운 태스크 파이프라인입니다.

Promise 를 사용하면 이렇게 구현할 수 있습니다:

new Promise(function(resolve, reject) {
    resolve(1);
}).then(function(res) {
    return new Promise(function(resolve, reject) {
        resolve(res + 1);
    });
}).then(function(res) {
    return new Promise(function(resolve, reject) {
        resolve(res + 1);
    });
}).then(function(res) {
    console.log(res);
});
// => 3

P.S. jQuery 의 $.when() 은 일종의 Promise 구현이지만 Promise A+ 사양과는 약간의 차이가 있습니다. 자세한 내용은 jQuery deffered 와 promise 객체 메서드 를 참조하세요.

삼. 실행 시에야 실행 순서를 확인할 수 있는 직렬 작업 처리

A, B, C 세 개의 작업이 있다고 가정합니다. 실행 순서는 불확실하며, BCA, BAC 등이 될 수 있습니다. 실행 시에야 비로소 세자의 실행 순서가 결정되지만, 순서가 결정되면 즉시 직렬로 실행해야 합니다.

이 경우 콜백 중첩은 더 이상 충족시킬 수 없습니다. 각 순서에 대해 하나의 콜백 중첩이 필요하기 때문입니다. 우리는 콜백을 자유롭게 조합할 수 있는 메커니즘이 필요합니다. 예를 들어 Promise 와 같은:

var tasks = getOrderedTasks();
var p = tasks[0];
tasks.forEach(function(item, index) {
    p.then(item);
});

이렇게 작성하면 부작용으로 파이프라인이 없어집니다 (작업의 출력을 다음 작업의 입력으로 전달할 수 없게 됨). 하지만 문제없습니다. 쉽게 수정할 수 있습니다:

function catchFuture(future) {
    // future 를 처리/전달
    // ...
    console.log('catchFuture: ' + future);
}
var tasks = getOrderedTasks();
var p = tasks[0];
tasks.forEach(function(item, index) {
    // p.then(item);
    p.then(item.then(catchFuture));
});

댓글

아직 댓글이 없습니다

댓글 작성