Find first available data source with jQuery Defer

2019-04-08 23:16发布

问题:

So I was asked this at an interview, but it brought up a good use case. Assume that you have a bunch of data sources. You want to find the first available one and process it and ignore the rest.

So something like:

var datasources = new Array("somedatabase1/pizza","somedatabase2/beer","somedatabase3/llama");
var dfds = new Array();
$.each(datasources,function(source){
    dfds.push($.getJSON(source));
});

$.when(dfds).done(function(){alert("they are all done");});

Ignore that I really don't think when accepts an array (maybe it does). This of course would make it wait till they are all completed. I am looking for some code that would make it wait until one, any of them is done, and then not worry about the others.

I was informed that it would only work recursively.

回答1:

This doesn't use recursion but fits the requirement to fetch from multiple datasources and only care about the first that returns a successful response.

http://jsfiddle.net/mNJ6D/

function raceToIt(urls) {
    var deferred = $.Deferred(),
        promises;

    function anyComplete(data) {
        if (!deferred.isResolved()) {
            deferred.resolveWith(this, [data]);
            promises.forEach(function(promise) {
                promise.abort();
            });
        }
    }
    promises = urls.map(function(url) {
        return $.getJSON(url).then(anyComplete);
    });
    return deferred.promise();
}
raceToIt(["/echo/json/", "/echo/json/", "/echo/json/"]).then(function(data) {
    console.log(data);
});​


回答2:

I've made a plugin which provides another version of $.when() with reversed semantics. It's modified from the actual jQuery implementation of $.when() so it's exactly the same as the original except that it waits for either the first resolved promise, or all promised to be rejected.

Just drop this code in right after you load jQuery:

(function($) {
  $.reverseWhen = function( subordinate /* , ..., subordinateN */ ) {
    var i = 0,
      rejectValues = Array.prototype.slice.call( arguments ),
      length = rejectValues.length,

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

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

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

      progressValues, progressContexts, rejectContexts;

    // add listeners to Deferred subordinates; treat others as rejected
    if ( length > 1 ) {
      progressValues = new Array( length );
      progressContexts = new Array( length );
      rejectContexts = new Array( length );
      for ( ; i < length; i++ ) {
        if ( rejectValues[ i ] && jQuery.isFunction( rejectValues[ i ].promise ) ) {
          rejectValues[ i ].promise()
            .done( deferred.resolve )
            .fail( updateFunc( i, rejectContexts, rejectValues ) )
            .progress( updateFunc( i, progressContexts, progressValues ) );
        } else {
          --remaining;
        }
      }
    }

    // if we're not waiting on anything, reject the master
    if ( !remaining ) {
      deferred.rejectWith( rejectContexts, rejectValues );
    }

    return deferred.promise();
  };
})(jQuery);