Home >Web Front-end >JS Tutorial >Detailed introduction to the Promise pattern sample code of JavaScript asynchronous programming

Detailed introduction to the Promise pattern sample code of JavaScript asynchronous programming

黄舟
黄舟Original
2017-03-11 15:19:571293browse

Asynchronous mode is becoming more and more important in web programming. For the mainstream web language Javascript, this mode is not very easy to implement. For this reason, many Javascript libraries (such as jQuery and Dojo) add a so-called It is an abstraction of promise (sometimes also called deferred). Through these libraries, developers can use the promise pattern in actual programming. The IE official blog recently published an article detailing how to use XMLHttpRequest2 to practice the promise mode. Let's take a look at the related concepts and applications.

Consider an example where a web page has asynchronous operations (through XMLHttpRequest2 or Web Workers). With the deepening of Web 2.0 technology, the browser side is bearing more and more computing pressure, so "concurrency" has a positive meaning. For developers, they must not only keep the interaction between the page and the user unaffected, but also coordinate the relationship between the page and asynchronous tasks. This kind of non-linear execution programming requirements presents difficulties in adapting. Putting aside page interaction, we can think of two results that need to be processed for asynchronous calls-successful operation and failure processing. After a successful call, we may need to use the returned result in another Ajax request, which will cause a "chain of functions" situation (detailed in the author's other article "Asynchronous Programming Style of NodeJS" explanation of). This situation creates programming complexity. Take a look at the following code example (based on 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);
 }

The function of the above code is to obtain the content with hashtags IE10 and IE9 in Twitter and display it on the page. This kind of nested callback function is difficult to understand. Developers need to carefully analyze which code is used for the application's business logic, and which code handles asynchronous function calls. The code structure is fragmented. Error handling is also decomposed. We need to detect the occurrence of errors in various places and handle them accordingly.

In order to reduce the complexity of asynchronous programming, developers have been looking for easy ways to handle asynchronous operations. One of these processing patterns is called a promise, which represents the result of an operation that may be long-running and does not necessarily have to be complete. Instead of blocking and waiting for a long operation to complete, this pattern returns an object that represents the promised result.

Consider an example where the page code needs to access a third-party API. Network delays may cause longer response times. In this case, using asynchronous programming will not affect the interaction between the entire page and the user. The promise mode usually implements a method called then to register the corresponding callback function when the state changes. For example, the following code example:

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

The promise mode is in one of the following three states at any time: unfulfilled, resolved, and rejected. Taking the CommonJS Promise/A standard as an example, the then method on the promise object is responsible for adding processing functions for the completed and rejected states. The then method will return another promise object to facilitate the formation of a promise pipeline. This method of returning promise objects can support developers to chain asynchronous operations, such as then(resolvedHandler, rejectedHandler);. The resolvedHandler callback function is triggered when the promise object enters the completion state and delivers the result; the rejectedHandler function is called in the rejection state.

With the promise pattern, we can reimplement the Twitter example above. In order to better understand the implementation method, we tried to build a promise pattern framework from scratch. First you need some objects to store promises.

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

Next, define the then method, which accepts two parameters for handling completion and rejection status.

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

At the same time, two methods are needed to execute the status transition from incomplete to completed and from incomplete to rejected.

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

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

Now that we have built a promise shelf, we can continue the above example, assuming that we only get the content of IE10. Create a method to send the Ajax request and wrap it in a promise. This promise object specifies the transition process of completion and rejection status in xhr.onload and xhr.onerror respectively. Please note that the searchTwitter function returns the promise object. Then, in loadTweets, use the then method to set the callback functions corresponding to the completion and rejection status.

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);
}

So far, we can apply the promise mode to a single Ajax request, but it seems that the advantages of promise are not reflected. Let's take a look at the concurrent collaboration of multiple Ajax requests. At this point, we need another method when to store the promise object ready to be called. Once a promise is converted from the incomplete state to the completed or rejected state, the corresponding handler function in the then method will be called. The when method is important when you need to wait for all operations to complete.

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

Take the scenario where we just obtained the content of IE10 and IE9 as an example. We can write the code like this:

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模块,具体的清单可以访问这里。

The above is the detailed content of Detailed introduction to the Promise pattern sample code of JavaScript asynchronous programming. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn