【问题标题】:Find first available data source with jQuery Deferred使用 jQuery Deferred 查找第一个可用的数据源
【发布时间】:2026-01-20 15:15:02
【问题描述】:

所以我在一次采访中被问到这个问题,但它提出了一个很好的用例。假设您有一堆数据源。您想找到第一个可用的并处理它并忽略其余的。

比如:

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

忽略我真的不认为什么时候接受一个数组(也许是这样)。这当然会让它等到它们全部完成。我正在寻找一些代码,让它等到一个,其中任何一个完成,然后不用担心其他的。

我被告知它只能递归地工作。

【问题讨论】:

  • 应该全部同时启动,还是第一次失败时第二次启动?
  • @Bergi 我认为两者都可以,我认为两者在各种用例中都是最佳的。
  • 是的,但他们的概念非常不同。那你需要哪一个?
  • 我为此向 jQuery 提出了另一个功能请求以及对$.when() 的相关增强:OPTIONS PARAMETER FOR $.WHEN() TO PROVIDE ALTERNATIVE SEMANTICS。它也已关闭,但 jaubourg 添加了很好的分析和建议,这将有助于任何对此问题感兴趣的人。
  • @hippietrail 太棒了!感谢您发布链接。

标签: javascript jquery jquery-deferred


【解决方案1】:

这不使用递归,但符合​​从多个数据源获取的要求,并且只关心返回成功响应的第一个。

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

【讨论】:

  • 这实际上非常棒,它可以让你一次启动它们!也许还有一种方法可以阻止其他查找。
  • @Parris 当然,只需将延迟存储在某处并在anyComplete 处理程序中调用abort。我已经编辑了。
【解决方案2】:

我制作了一个插件,它提供了另一个版本的$.when() 具有相反的语义。它是从 $.when() 的实际 jQuery 实现修改而来的,因此它与原始版本完全相同,只是它等待第一个 resolved 承诺,或全部承诺为 rejected。

只需在加载 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);

【讨论】: