>  기사  >  웹 프론트엔드  >  JavaScript 비동기 프로그래밍의 Promise 패턴 샘플 코드에 대한 자세한 소개

JavaScript 비동기 프로그래밍의 Promise 패턴 샘플 코드에 대한 자세한 소개

黄舟
黄舟원래의
2017-03-11 15:19:571266검색

비동기 모드는 웹 프로그래밍에서 점점 더 중요해지고 있습니다. 주류 웹 언어인 Javascript의 경우 이 모드는 구현하기가 그리 쉽지 않습니다. 이러한 이유로 많은 Javascript 라이브러리(예: jQuery 및 Dojo)에서는 소위 말하는 모드를 추가합니다. 이는 약속의 추상화입니다(때때로 지연이라고도 함). 이러한 라이브러리를 통해 개발자는 실제 프로그래밍에서 Promise 패턴을 사용할 수 있습니다. IE 공식 블로그는 최근 XMLHttpRequest2를 사용하여 약속 모드를 실행하는 방법을 자세히 설명하는 기사를 게시했습니다. 관련 개념과 적용 사례를 살펴보겠습니다.

웹 페이지에 비동기 작업(XMLHttpRequest2 또는 Web Workers를 통해)이 있는 예를 생각해 보세요. Web 2.0 기술이 심화됨에 따라 브라우저 측에서는 점점 더 많은 컴퓨팅 압력을 받고 있으므로 "동시성"은 긍정적인 의미를 갖습니다. 개발자의 경우 페이지와 사용자 간의 상호 작용을 영향 없이 유지해야 할 뿐만 아니라 페이지와 비동기 작업 간의 관계를 조정해야 합니다. 이러한 종류의 비선형 실행 프로그래밍 요구 사항은 적응하기 어렵습니다. 페이지 상호 작용을 제쳐두고 비동기 호출을 위해 처리해야 하는 두 가지 결과, 즉 성공적인 작업과 실패 처리를 생각해 볼 수 있습니다. 호출이 성공한 후 다른 Ajax 요청에서 반환된 결과를 사용해야 할 수도 있습니다. 그러면 "함수 체인" 상황이 발생합니다(저자의 다른 기사 "NodeJS의 비동기 프로그래밍 스타일" 설명에 자세히 설명되어 있음). 이러한 상황은 프로그래밍을 복잡하게 만듭니다. 다음 코드 예제를 살펴보십시오(XMLHttpRequest2 기반):

function searchTwitter(term, onload, onerror) {

     var xhr, results, url;
     url = 'http://search.twitter.com/search.json?rpp=100&q=' + term;
     xhr = new XMLHttpRequest();
     xhr.open('GET', url, true);

     xhr.onload = function (e) {
         if (this.status === 200) {
             results = JSON.parse(this.responseText);
             onload(results);
         }
     };

     xhr.onerror = function (e) {
         onerror(e);
     };

     xhr.send();
 }

 function handleError(error) {
     /* handle the error */
 }

 function concatResults() {
     /* order tweets by date */
 }

 function loadTweets() {
     var container = document.getElementById('container');

     searchTwitter('#IE10', function (data1) {
         searchTwitter('#IE9', function (data2) {
             /* Reshuffle due to date */
             var totalResults = concatResults(data1.results, data2.results);
             totalResults.forEach(function (tweet) {
                 var el = document.createElement('li');
                 el.innerText = tweet.text;
                 container.appendChild(el);
             });
         }, handleError);
     }, handleError);
 }

위 코드의 기능은 해시태그 IE10 및 IE9가 포함된 Twitter 콘텐츠를 가져와서 페이지에 표시하는 것입니다. 이런 종류의 중첩된 콜백 함수는 이해하기 어렵습니다. 개발자는 어떤 코드가 애플리케이션의 비즈니스 로직에 사용되는지, 어떤 코드가 비동기 함수 호출을 처리하는지 주의 깊게 분석해야 합니다. 오류 처리도 분해되어 다양한 곳에서 오류 발생을 감지하고 그에 따라 처리해야 합니다.

비동기 프로그래밍의 복잡성을 줄이기 위해 개발자들은 비동기 작업을 처리하는 쉬운 방법을 찾고 있었습니다. 이러한 처리 패턴 중 하나를 약속(promise)이라고 하며, 이는 장기간 실행될 수 있고 반드시 완료될 필요는 없는 작업의 결과를 나타냅니다. 이 패턴은 긴 작업이 완료될 때까지 차단하고 기다리는 대신 약속된 결과를 나타내는 개체를 반환합니다.

페이지 코드가 타사 API에 액세스해야 하는 경우를 생각해 보세요. 네트워크 지연으로 인해 응답 시간이 길어질 수 있습니다. 이 경우 비동기 프로그래밍을 사용하면 전체 페이지와 사용자 간의 상호 작용에 영향을 미치지 않습니다. Promise 모드는 일반적으로 상태가 변경될 때 해당 콜백 함수를 등록하기 위해 then 호출되는 메서드를 구현합니다. 예를 들어, 다음 코드 예는 다음과 같습니다.

searchTwitter(term).then(filterResults).then(displayResults);

프로미스 모드는 언제든지 이행되지 않음, 해결됨, 거부됨의 세 가지 상태 중 하나입니다. CommonJS Promise/A 표준을 예로 들면, Promise 객체의 then 메소드는 완료 및 거부된 상태에 대한 처리 기능을 추가하는 역할을 합니다. then 메소드는 Promise 파이프라인의 형성을 용이하게 하기 위해 또 다른 Promise 객체를 반환합니다. Promise 객체를 반환하는 이 메소드는 개발자가 then(resolvedHandler, RejectedHandler);와 같은 비동기 작업을 연결하도록 지원할 수 있습니다. ResolvedHandler 콜백 함수는 Promise 객체가 완료 상태에 진입하고 결과를 전달할 때 트리거됩니다. RejectedHandler 함수는 거부 상태에서 호출됩니다.

Promise 패턴을 사용하면 위의 Twitter 예제를 다시 구현할 수 있습니다. 구현 방법을 더 잘 이해하기 위해 처음부터 Promise 패턴 프레임워크를 구축하려고 했습니다. 먼저 약속을 저장할 객체가 필요합니다.

var Promise = function () {
        /* initialize promise */
    };

다음으로 완료 및 거부 상태를 처리하기 위해 두 개의 매개 변수를 받는 then 메서드를 정의합니다.

Promise.prototype.then = function (onResolved, onRejected) {
     /* invoke handlers based upon state transition */
 };

동시에 미완료에서 완료로, 미완료에서 거부로 상태 전환을 실행하려면 두 가지 방법이 필요합니다.

Promise.prototype.resolve = function (value) {
     /* move from unfulfilled to resolved */
 };

 Promise.prototype.reject = function (error) {
     /* move from unfulfilled to rejected */
 };

이제 Promise Shelf가 구축되었으므로 IE10의 콘텐츠만 가져온다고 가정하고 위의 예를 계속 진행할 수 있습니다. Ajax 요청을 보내고 이를 Promise로 래핑하는 메서드를 만듭니다. 이 promise 객체는 xhr.onload 및 xhr.onerror에서 각각 완료 및 거부 상태의 전환 프로세스를 지정합니다. searchTwitter 함수는 promise 객체를 반환합니다. 그런 다음 loadTweets에서 then 메소드를 사용하여 완료 및 거부 상태에 해당하는 콜백 함수를 설정합니다.

function searchTwitter(term) {

    var url, xhr, results, promise;
    url = 'http://search.twitter.com/search.json?rpp=100&q=' + term;
    promise = new Promise();
    xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);

    xhr.onload = function (e) {
        if (this.status === 200) {
            results = JSON.parse(this.responseText);
            promise.resolve(results);
        }
    };

    xhr.onerror = function (e) {
        promise.reject(e);
    };

    xhr.send();
    return promise;
}

function loadTweets() {
    var container = document.getElementById('container');
    searchTwitter('#IE10').then(function (data) {
        data.results.forEach(function (tweet) {
            var el = document.createElement('li');
            el.innerText = tweet.text;
            container.appendChild(el);
        });
    }, handleError);
}

지금까지는 단일 Ajax 요청에 Promise 모드를 적용할 수 있었지만 Promise의 장점이 반영되지 않은 것 같습니다. 여러 Ajax 요청의 동시 협업을 살펴보겠습니다. 이 시점에서 호출 준비가 된 Promise 개체를 저장할 때 또 다른 메서드가 필요합니다. Promise가 불완전 상태에서 완료 또는 거부 상태로 변환되면 then 메소드의 해당 핸들러 함수가 호출됩니다. 모든 작업이 완료될 때까지 기다려야 하는 경우 when 메서드가 중요합니다.

Promise.when = function () {
    /* handle promises arguments and queue each */
};

방금 IE10과 IE9의 콘텐츠를 얻은 시나리오를 예로 들면 다음과 같이 코드를 작성할 수 있습니다.

var container, promise1, promise2;
container = document.getElementById('container');
promise1 = searchTwitter('#IE10');
promise2 = searchTwitter('#IE9');
Promise.when(promise1, promise2).then(function (data1, data2) {

    /* Reshuffle due to date */
    var totalResults = concatResults(data1.results, data2.results);
    totalResults.forEach(function (tweet) {
        var el = document.createElement('li');
        el.innerText = tweet.text;
        container.appendChild(el);
    });
}, handleError);

分析上面的代码可知,when函数会等待两个promise对象的状态发生变化再做具体的处理。在实际的Promise库中,when函数有很多变种,比如 when.some()、when.all()、when.any()等,读者从函数名字中大概能猜出几分意思来,详细的说明可以参考CommonJS的一个promise实现when.js。

除了CommonJS,其他主流的Javascript框架如jQuery、Dojo等都存在自己的promise实现。开发人员应该好好利用这种模式来降低异步编程的复杂性。我们选取Dojo为例,看一看它的实现有什么异同。

Dojo框架里实现promise模式的对象是Deferred,该对象也有then函数用于处理完成和拒绝状态并支持串联,同时还有resolve和reject,功能如之前所述。下面的代码完成了Twitter的场景:

function searchTwitter(term) {

    var url, xhr, results, def;
    url = 'http://search.twitter.com/search.json?rpp=100&q=' + term;
    def = new dojo.Deferred();
    xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);

    xhr.onload = function (e) {
        if (this.status === 200) {
            results = JSON.parse(this.responseText);
            def.resolve(results);
        }
    };

    xhr.onerror = function (e) {
        def.reject(e);
    };

    xhr.send();
    return def;
}

dojo.ready(function () {
    var container = dojo.byId('container');
    searchTwitter('#IE10').then(function (data) {
        data.results.forEach(function (tweet) {
            dojo.create('li', {
                innerHTML: tweet.text
            }, container);
        });
    });
});

不仅如此,类似dojo.xhrGet方法返回的即是dojo.Deferred对象,所以无须自己包装promise模式。

var deferred = dojo.xhrGet({
    url: "search.json",
    handleAs: "json"
});

deferred.then(function (data) {
    /* handle results */
}, function (error) {
    /* handle error */
});

除此之外,Dojo还引入了dojo.DeferredList,支持开发人员同时处理多个dojo.Deferred对象,这其实就是上面所提到的when方法的另一种表现形式。

dojo.require("dojo.DeferredList");
dojo.ready(function () {
    var container, def1, def2, defs;
    container = dojo.byId('container');
    def1 = searchTwitter('#IE10');
    def2 = searchTwitter('#IE9');

    defs = new dojo.DeferredList([def1, def2]);

    defs.then(function (data) {
        // Handle exceptions
        if (!results[0][0] || !results[1][0]) {
            dojo.create("li", {
                innerHTML: 'an error occurred'
            }, container);
            return;
        }
        var totalResults = concatResults(data[0][1].results, data[1][1].results);

        totalResults.forEach(function (tweet) {
            dojo.create("li", {
                innerHTML: tweet.text
            }, container);
        });
    });
});

上面的代码比较清楚,不再详述。

说到这里,读者可能已经对promise模式有了一个比较完整的了解,异步编程会变得越来越重要,在这种情况下,我们需要找到办法来降低复杂度,promise模式就是一个很好的例子,它的风格比较人性化,而且主流的JS框架提供了自己的实现。所以在编程实践中,开发人员应该尝试这种便捷的编程技巧。需要注意的是,promise模式的使用需要恰当地设置promise对象,在对应的事件中调用状态转换函数,并且在最后返回promise对象。

技术社区对异步编程的关注也在升温,国内社区也发出了自己的声音。资深技术专家老赵就发布了一套开源的异步开发辅助库Jscex,它的设计很巧妙,抛弃了回调函数的编程方式,采用一种“线性编码、异步执行”的思想,感兴趣的读者可以查看这里。

不仅仅是前端的JS库,如今火热的NodeJS平台也出现了许多第三方的promise模块,具体的清单可以访问这里。

위 내용은 JavaScript 비동기 프로그래밍의 Promise 패턴 샘플 코드에 대한 자세한 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.