Table of Contents
使用 Deferred
jQuery 中 Deferred 的使用
实现 Deferred
Home Web Front-end JS Tutorial jquery 之 Deferred 使用与实现

jquery 之 Deferred 使用与实现

Jun 01, 2016 am 09:54 AM
jquery

观察者模式是开发中经常使用的模式,这个模式由两个主要部分组成:主题和观察者。通过观察者模式,实现主题和观察者的解耦。

主题负责发布内容,而观察者则接收主题发布的内容。通常情况下,观察者都是多个,所以,我们需要一个集合来保存所有的观察者,在主题发布内容之后,依次将主题发布的内容提供给观察者,从程序的角度来说,观察者就是一堆的方法,我们将内容作为参数依次调用这些方法。

如果了解 jQuery 的 Callbacks, 那么,你会发现,通过 Callbacks 来管理观察者的列表是很方便的事情。再添加一个主题,我们就可以实现观察者模式了。在 jQuery 中,这个模式被到处使用,不要说你没有使用过 ready 函数,回想一下,你可以在一个页面中,多次使用 ready 函数,在 ready 事件触发之后,这些函数就可以被依次调用了,这个机制就已经突破了简单的事件处理机制了。

需要说明的是,许多事件仅仅触发一次,比如 ready 事件,ajax 的请求处理等等,这种情况下,使用 Deferred 就非常方便了。

 

使用 Deferred

在 jQuery 中,实现观察者模式的就是 Deferred 了,我们先看它的使用。你也可以直接看 jQuery 的 Deferred 文档。

这个对象提供了主题和订阅的管理,使用它可以很容易实现一次性的观察者模式。

<code class="javascript">// 定义主题
var subject = (function(){
    var dfd = $.Deferred();
    
    return dfd;
})();

// 两个观察者
var fn1 = function(content){
    console.log("fn1: " + content );
}

var fn2 = function(content){
    console.log("fn2: " + content );
}

// 注册观察者
$.when( subject )
.done( fn1 )
.done( fn2 );

// 发布内容
subject.resolve("Alice");</code>
Copy after login

通常我们在主题内部来决定什么时候,以及发布什么内容,而不允许在主题之外发布。通过 Deferred 对象的 promise 方法,我们可以只允许在主题之外注册观察者,有点像 .NET 中 event 的处理了。这样,我们的代码就成为下面的形式。

<code class="javascript">// 定义主题
var subject = (function(){
    var dfd = $.Deferred();
    
    var task = function()
    {
        // 发布内容
        dfd.resolve("Alice");
    }
    
    setTimeout( task, 3000);
    
    return dfd.promise();
})();

// 两个观察者
var fn1 = function(content){
    console.log("fn1: " + content );
}

var fn2 = function(content){
    console.log("fn2: " + content );
}

// 注册观察者
$.when( subject )
.done( fn1 )
.done( fn2 );</code>
Copy after login

在 jQuery 中,甚至可以提供两个主题同时被观察, 需要注意的是,要等两个主题都触发之后,才会真正触发,每个观察者一次得到这两个主题,所以参数变成了 2 个。

<code class="javascript">// 定义主题
var subjectAlice = (function(){
    var dfd = $.Deferred();
    
    var task = function()
    {
        // 发布内容
        dfd.resolve("Alice");
    }
    
    setTimeout( task, 3000);
    
    return dfd.promise();
})();

var subjectTom = (function(){
    var dfd = $.Deferred();
    
    var task = function()
    {
        // 发布内容
        dfd.resolve("Tom");
    }
    
    setTimeout( task, 1000);
    
    return dfd.promise();
})();

// 两个观察者
var fn1 = function(content1, content2){
    console.log("fn1: " + content1 );
    console.log("fn1: " + content2 );
}

var fn2 = function(content1, content2){
    console.log("fn2: " + content1 );
    console.log("fn2: " + content2 );
}

// 注册观察者
$.when( subjectAlice, subjectTom )
.done( fn1 )
.done( fn2 );</code>
Copy after login

实际上,在 jQuery 中,不仅可以发布成功完成的事件,主题还可以发布其它两种事件:失败和处理中。

失败事件,通过调用主题的 reject 方法可以发布失败的消息,对于观察者来说,需要通过 fail 来注册这个事件了。

处理中事件,通过调用主题的 notify 来发布处理中的消息,对于观察者来说,需要通过 progress 来注册这个事件。

要是观察者想一次性注册多个事件,那么,可以通过 then 来注册,这种方式可以处理主题的成功、失败和处理中三种事件。

<code class="javascript">$.get( "test.php" ).then(
  function() {
    alert( "$.get succeeded" );
  }, function() {
    alert( "$.get failed!" );
  }
);</code>
Copy after login

只考虑成功和失败的话,就通过 always 来处理。

<code class="javascript">$.get( "test.php" )
.always(function() {
  alert( "$.get completed with success or error callback arguments" );
});</code>
Copy after login

 

jQuery 中 Deferred 的使用

常用的是 ajax, get, post 等等 Ajax 函数了。它们内部都已经实现为了 Deferred ,返回的结果就是 Deferred 对象了。也就是说你只管写观察者就可以了,主题内部已经处理好了,比如当 ajax 成功之后调用 resolve 来触发 done 事件并传递参数的问题。你可以继续使用传统的回调方式,显然推荐你使用 Deferred 方式了。这样你的代码结构更加清晰。

<code class="javascript">$.post( "test.php", { name: "John", time: "2pm" })
  .done(function( data ) {
    alert( "Data Loaded: " + data );
  });</code>
Copy after login

如果需要访问两个 Ajax ,则可以这样

<code class="javascript">$.when( $.post( "test.php", { name: "John", time: "2pm" }),
        $.post( "other.php" ) )
  .done(function( data1, data2 ) {
    alert( "Data Loaded: " + data1 );
    alert( "Data Loaded: " + data2 );
  });</code>
Copy after login

 

实现 Deferred

通过前面的使用,其实你一定可以想到,在 Deferred 这个对象的内部,必须有三个回调队列了,这里的成功和失败只能一次完成,所以这两个 Callbacks 都使用了 once 来定义。

<code class="javascript">var tuples = [
        // action, add listener, listener list, final state
        [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
        [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
        [ "notify", "progress", jQuery.Callbacks("memory") ]
            ],</code>
Copy after login

当前处理的状态。

<code class="javascript">state = "pending",
promise = {
    state: function() {
        return state;
    },</code>
Copy after login

always 就是直接注册了两个事件。then 允许我们一次处理三种注册。

<code class="javascript">always: function() {
        deferred.done( arguments ).fail( arguments );
        return this;
    },
    then: function( /* fnDone, fnFail, fnProgress */ ) {
        var fns = arguments;
        return jQuery.Deferred(function( newDefer ) {
            jQuery.each( tuples, function( i, tuple ) {
                var action = tuple[ 0 ],
                    fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
                // deferred[ done | fail | progress ] for forwarding actions to newDefer
                deferred[ tuple[1] ](function() {
                    var returned = fn && fn.apply( this, arguments );
                    if ( returned && jQuery.isFunction( returned.promise ) ) {
                        returned.promise()
                            .done( newDefer.resolve )
                            .fail( newDefer.reject )
                            .progress( newDefer.notify );
                    } else {
                        newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
                    }
                });
            });
            fns = null;
        }).promise();
    },</code>
Copy after login

deferred 就是一个对象。pipe 是已经过时的用法,是 then 的别名。

<code class="javascript">deferred = {};

    // Keep pipe for back-compat
    promise.pipe = promise.then;

    // Add list-specific methods
    jQuery.each( tuples, function( i, tuple ) {
        var list = tuple[ 2 ],
            stateString = tuple[ 3 ];

        // promise[ done | fail | progress ] = list.add
        promise[ tuple[1] ] = list.add;

        // Handle state
        if ( stateString ) {
            list.add(function() {
                // state = [ resolved | rejected ]
                state = stateString;

            // [ reject_list | resolve_list ].disable; progress_list.lock
            }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
        }

        // deferred[ resolve | reject | notify ]
        deferred[ tuple[0] ] = function() {
            deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
            return this;
        };
        deferred[ tuple[0] + "With" ] = list.fireWith;
    });

    // Make the deferred a promise
    promise.promise( deferred );

    // Call given func if any
    if ( func ) {
        func.call( deferred, deferred );
    }

    // All done!
    return deferred;
},</code>
Copy after login

when 是一个助手方法,支持多个主题。

<code class="javascript">// Deferred helper
when: function( subordinate /* , ..., subordinateN */ ) {
    var i = 0,
        resolveValues = core_slice.call( arguments ),
        length = resolveValues.length,

        // the count of uncompleted subordinates
        remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,

        // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
        deferred = remaining === 1 ? subordinate : jQuery.Deferred(),

        // Update function for both resolve and progress values
        updateFunc = function( i, contexts, values ) {
            return function( value ) {
                contexts[ i ] = this;
                values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
                if( values === progressValues ) {
                    deferred.notifyWith( contexts, values );
                } else if ( !( --remaining ) ) {
                    deferred.resolveWith( contexts, values );
                }
            };
        },

        progressValues, progressContexts, resolveContexts;

    // add listeners to Deferred subordinates; treat others as resolved
    if ( length > 1 ) {
        progressValues = new Array( length );
        progressContexts = new Array( length );
        resolveContexts = new Array( length );
        for ( ; i </code>
Copy after login

完整的代码如下所示:

<code class="javascript">jQuery.extend({

    Deferred: function( func ) {
        var tuples = [
                // action, add listener, listener list, final state
                [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
                [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
                [ "notify", "progress", jQuery.Callbacks("memory") ]
            ],
            state = "pending",
            promise = {
                state: function() {
                    return state;
                },
                always: function() {
                    deferred.done( arguments ).fail( arguments );
                    return this;
                },
                then: function( /* fnDone, fnFail, fnProgress */ ) {
                    var fns = arguments;
                    return jQuery.Deferred(function( newDefer ) {
                        jQuery.each( tuples, function( i, tuple ) {
                            var action = tuple[ 0 ],
                                fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
                            // deferred[ done | fail | progress ] for forwarding actions to newDefer
                            deferred[ tuple[1] ](function() {
                                var returned = fn && fn.apply( this, arguments );
                                if ( returned && jQuery.isFunction( returned.promise ) ) {
                                    returned.promise()
                                        .done( newDefer.resolve )
                                        .fail( newDefer.reject )
                                        .progress( newDefer.notify );
                                } else {
                                    newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
                                }
                            });
                        });
                        fns = null;
                    }).promise();
                },
                // Get a promise for this deferred
                // If obj is provided, the promise aspect is added to the object
                promise: function( obj ) {
                    return obj != null ? jQuery.extend( obj, promise ) : promise;
                }
            },
            deferred = {};

        // Keep pipe for back-compat
        promise.pipe = promise.then;

        // Add list-specific methods
        jQuery.each( tuples, function( i, tuple ) {
            var list = tuple[ 2 ],
                stateString = tuple[ 3 ];

            // promise[ done | fail | progress ] = list.add
            promise[ tuple[1] ] = list.add;

            // Handle state
            if ( stateString ) {
                list.add(function() {
                    // state = [ resolved | rejected ]
                    state = stateString;

                // [ reject_list | resolve_list ].disable; progress_list.lock
                }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
            }

            // deferred[ resolve | reject | notify ]
            deferred[ tuple[0] ] = function() {
                deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
                return this;
            };
            deferred[ tuple[0] + "With" ] = list.fireWith;
        });

        // Make the deferred a promise
        promise.promise( deferred );

        // Call given func if any
        if ( func ) {
            func.call( deferred, deferred );
        }

        // All done!
        return deferred;
    },

    // Deferred helper
    when: function( subordinate /* , ..., subordinateN */ ) {
        var i = 0,
            resolveValues = core_slice.call( arguments ),
            length = resolveValues.length,

            // the count of uncompleted subordinates
            remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,

            // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
            deferred = remaining === 1 ? subordinate : jQuery.Deferred(),

            // Update function for both resolve and progress values
            updateFunc = function( i, contexts, values ) {
                return function( value ) {
                    contexts[ i ] = this;
                    values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
                    if( values === progressValues ) {
                        deferred.notifyWith( contexts, values );
                    } else if ( !( --remaining ) ) {
                        deferred.resolveWith( contexts, values );
                    }
                };
            },

            progressValues, progressContexts, resolveContexts;

        // add listeners to Deferred subordinates; treat others as resolved
        if ( length > 1 ) {
            progressValues = new Array( length );
            progressContexts = new Array( length );
            resolveContexts = new Array( length );
            for ( ; i </code>
Copy after login

Statement of this Website
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

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Detailed explanation of jQuery reference methods: Quick start guide Detailed explanation of jQuery reference methods: Quick start guide Feb 27, 2024 pm 06:45 PM

Detailed explanation of jQuery reference method: Quick start guide jQuery is a popular JavaScript library that is widely used in website development. It simplifies JavaScript programming and provides developers with rich functions and features. This article will introduce jQuery's reference method in detail and provide specific code examples to help readers get started quickly. Introducing jQuery First, we need to introduce the jQuery library into the HTML file. It can be introduced through a CDN link or downloaded

How to use PUT request method in jQuery? How to use PUT request method in jQuery? Feb 28, 2024 pm 03:12 PM

How to use PUT request method in jQuery? In jQuery, the method of sending a PUT request is similar to sending other types of requests, but you need to pay attention to some details and parameter settings. PUT requests are typically used to update resources, such as updating data in a database or updating files on the server. The following is a specific code example using the PUT request method in jQuery. First, make sure you include the jQuery library file, then you can send a PUT request via: $.ajax({u

In-depth analysis: jQuery's advantages and disadvantages In-depth analysis: jQuery's advantages and disadvantages Feb 27, 2024 pm 05:18 PM

jQuery is a fast, small, feature-rich JavaScript library widely used in front-end development. Since its release in 2006, jQuery has become one of the tools of choice for many developers, but in practical applications, it also has some advantages and disadvantages. This article will deeply analyze the advantages and disadvantages of jQuery and illustrate it with specific code examples. Advantages: 1. Concise syntax jQuery's syntax design is concise and clear, which can greatly improve the readability and writing efficiency of the code. for example,

How to remove the height attribute of an element with jQuery? How to remove the height attribute of an element with jQuery? Feb 28, 2024 am 08:39 AM

How to remove the height attribute of an element with jQuery? In front-end development, we often encounter the need to manipulate the height attributes of elements. Sometimes, we may need to dynamically change the height of an element, and sometimes we need to remove the height attribute of an element. This article will introduce how to use jQuery to remove the height attribute of an element and provide specific code examples. Before using jQuery to operate the height attribute, we first need to understand the height attribute in CSS. The height attribute is used to set the height of an element

jQuery Tips: Quickly modify the text of all a tags on the page jQuery Tips: Quickly modify the text of all a tags on the page Feb 28, 2024 pm 09:06 PM

Title: jQuery Tips: Quickly modify the text of all a tags on the page In web development, we often need to modify and operate elements on the page. When using jQuery, sometimes you need to modify the text content of all a tags in the page at once, which can save time and energy. The following will introduce how to use jQuery to quickly modify the text of all a tags on the page, and give specific code examples. First, we need to introduce the jQuery library file and ensure that the following code is introduced into the page: &lt

Use jQuery to modify the text content of all a tags Use jQuery to modify the text content of all a tags Feb 28, 2024 pm 05:42 PM

Title: Use jQuery to modify the text content of all a tags. jQuery is a popular JavaScript library that is widely used to handle DOM operations. In web development, we often encounter the need to modify the text content of the link tag (a tag) on ​​the page. This article will explain how to use jQuery to achieve this goal, and provide specific code examples. First, we need to introduce the jQuery library into the page. Add the following code in the HTML file:

Understand the role and application scenarios of eq in jQuery Understand the role and application scenarios of eq in jQuery Feb 28, 2024 pm 01:15 PM

jQuery is a popular JavaScript library that is widely used to handle DOM manipulation and event handling in web pages. In jQuery, the eq() method is used to select elements at a specified index position. The specific usage and application scenarios are as follows. In jQuery, the eq() method selects the element at a specified index position. Index positions start counting from 0, i.e. the index of the first element is 0, the index of the second element is 1, and so on. The syntax of the eq() method is as follows: $("s

How to tell if a jQuery element has a specific attribute? How to tell if a jQuery element has a specific attribute? Feb 29, 2024 am 09:03 AM

How to tell if a jQuery element has a specific attribute? When using jQuery to operate DOM elements, you often encounter situations where you need to determine whether an element has a specific attribute. In this case, we can easily implement this function with the help of the methods provided by jQuery. The following will introduce two commonly used methods to determine whether a jQuery element has specific attributes, and attach specific code examples. Method 1: Use the attr() method and typeof operator // to determine whether the element has a specific attribute

See all articles