jquery 之 Deferred 使用与实现

来源:互联网 发布:知秋的小说哪本好看 编辑:程序博客网 时间:2024/05/16 17:46

使用 Deferred

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

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

复制代码
// 定义主题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");
复制代码

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

复制代码
// 定义主题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 );
复制代码

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

复制代码
// 定义主题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 );
复制代码

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

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

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

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

复制代码
$.get( "test.php" ).then(  function() {    alert( "$.get succeeded" );  }, function() {    alert( "$.get failed!" );  });
复制代码

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

$.get( "test.php" ).always(function() {  alert( "$.get completed with success or error callback arguments" );});

 

jQuery 中 Deferred 的使用

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

$.post( "test.php", { name: "John", time: "2pm" })  .done(function( data ) {    alert( "Data Loaded: " + data );  });

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

$.when( $.post( "test.php", { name: "John", time: "2pm" }),        $.post( "other.php" ) )  .done(function( data1, data2 ) {    alert( "Data Loaded: " + data1 );    alert( "Data Loaded: " + data2 );  });

 

实现 Deferred

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

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 就是直接注册了两个事件。then 允许我们一次处理三种注册。

复制代码
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();    },
复制代码

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

复制代码
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;},
复制代码

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

复制代码
// Deferred helperwhen: 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 < length; i++ ) {            if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {                resolveValues[ i ].promise()                    .done( updateFunc( i, resolveContexts, resolveValues ) )                    .fail( deferred.reject )                    .progress( updateFunc( i, progressContexts, progressValues ) );            } else {                --remaining;            }        }    }    // if we're not waiting on anything, resolve the master    if ( !remaining ) {        deferred.resolveWith( resolveContexts, resolveValues );    }    return deferred.promise();}
复制代码

完整的代码如下所示:

复制代码
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 < length; i++ ) {                if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {                    resolveValues[ i ].promise()                        .done( updateFunc( i, resolveContexts, resolveValues ) )                        .fail( deferred.reject )                        .progress( updateFunc( i, progressContexts, progressValues ) );                } else {                    --remaining;                }            }        }        // if we're not waiting on anything, resolve the master        if ( !remaining ) {            deferred.resolveWith( resolveContexts, resolveValues );        }        return deferred.promise();    }});
0 0
原创粉丝点击