【问题标题】:How can jQuery deferred be used?如何使用 jQuery 延迟?
【发布时间】:2011-06-19 16:03:34
【问题描述】:

jQuery 1.5 带来了新的 Deferred 对象和附加方法 .when.Deferred._Deferred

对于之前没有使用过.Deferred的人,我已经注释了source for it

这些新方法有哪些可能的用途,我们如何将它们融入模式?

我已经阅读了APIsource,所以我知道它的作用。我的问题是我们如何在日常代码中使用这些新功能?

我有一个简单的example 缓冲区类,它按顺序调用 AJAX 请求。 (上一个完成后下一个开始)。

/* Class: Buffer
 *  methods: append
 *
 *  Constructor: takes a function which will be the task handler to be called
 *
 *  .append appends a task to the buffer. Buffer will only call a task when the 
 *  previous task has finished
 */
var Buffer = function(handler) {
    var tasks = [];
    // empty resolved deferred object
    var deferred = $.when();

    // handle the next object
    function handleNextTask() {
        // if the current deferred task has resolved and there are more tasks
        if (deferred.isResolved() && tasks.length > 0) {
            // grab a task
            var task = tasks.shift();
            // set the deferred to be deferred returned from the handler
            deferred = handler(task);
            // if its not a deferred object then set it to be an empty deferred object
            if (!(deferred && deferred.promise)) {
                deferred = $.when();
            }
            // if we have tasks left then handle the next one when the current one 
            // is done.
            if (tasks.length > 0) {
                deferred.done(handleNextTask);
            }
        }
    }

    // appends a task.
    this.append = function(task) {
        // add to the array
        tasks.push(task);
        // handle the next task
        handleNextTask();
    };
};

我正在寻找 .Deferred.when 的演示和可能用途。

如果看到._Deferred 的例子也很高兴。

链接到新的jQuery.ajax 源示例是作弊。

当我们抽象出一个操作是同步完成还是异步完成时,我对哪些技术可用特别感兴趣。

【问题讨论】:

  • 来自常见问题解答:避免提出主观问题...每个答案都同样有效:“您最喜欢什么______?” (他们的重点)
  • @T.J.Crowser 我会考虑改写它。
  • 这是一个很好的问题,但不可能有 很多人可以回答 :-)
  • @Pointy 我主要看那些在它是 3rd 方插件时使用它的人。并鼓励人们坐下来使用它!
  • ._Deferred 只是.Deferred 使用的真正“延迟对象”。这是一个您很可能永远不需要的内部对象。

标签: javascript jquery jquery-deferred


【解决方案1】:

我能想到的最佳用例是缓存 AJAX 响应。这是来自Rebecca Murphey's intro post on the topic的修改示例:

var cache = {};

function getData( val ){

    // return either the cached value or jqXHR object wrapped Promise
    return $.when(
        cache[ val ] || 
        $.ajax('/foo/', {
            data: { value: val },
            dataType: 'json',
            success: function( resp ){
                cache[ val ] = resp;
            }
        })
    );
}

getData('foo').then(function(resp){
    // do something with the response, which may
    // or may not have been retrieved using an
    // XHR request.
});

基本上,如果该值在立即从缓存中返回之前已经被请求过一次。否则,AJAX 请求会获取数据并将其添加到缓存中。 $.when/.then 不关心这些;所有你需要关心的是使用响应,在这两种情况下都会传递给.then() 处理程序。 jQuery.when() 将非 Promise/Deferred 处理为已完成的,立即执行链上的任何 .done().then()

当任务可能异步操作也可能不异步操作,并且您希望从代码中抽象出该条件时,延迟是完美的。

另一个使用 $.when 助手的真实示例:

$.when($.getJSON('/some/data/'), $.get('template.tpl')).then(function (data, tmpl) {

    $(tmpl) // create a jQuery object out of the template
    .tmpl(data) // compile it
    .appendTo("#target"); // insert it into the DOM

});

【讨论】:

  • 两个出色的例子。我实现了与第二个类似的东西,但有 4 个 ajax 请求,它表现良好,而且更加清晰、紧凑、逻辑、可维护等。jQuery.Deferred 是一个真正的好东西。
  • 这是一个关于这个主题的有用视频bigbinary.com/videos/3-using-deferred-in-jquery
  • 如果结果是假值,缓存将不起作用。我也不喜欢 getData 根据所采用的分支返回 2 种不同类型的事实。
  • 请参阅下面 Julian D. 的回答以获得更好的 ajax 缓存实现。
  • 我不明白第一个代码示例是如何工作的:我了解对象未缓存的情况,但如果是,则不会 cache[ val ] 不会返回承诺(jquery 文档说参数是发件人返回的数据)意思是.then的成员访问会报错……对吧?我错过了什么?
【解决方案2】:

这里是一个与ehynd's answer 略有不同的 AJAX 缓存实现。

fortuneRice's follow-up question 中所述,如果请求在其中一个返回之前执行,ehynd 的实现实际上并没有阻止多个相同的请求。也就是说,

for (var i=0; i<3; i++) {
    getData("xxx");
}

如果之前没有缓存过“xxx”的结果,很可能会产生 3 个 AJAX 请求。

这可以通过缓存请求的延迟而不是结果来解决:

var cache = {};

function getData( val ){

    // Return a promise from the cache (if available)
    // or create a new one (a jqXHR object) and store it in the cache.
    var promise = cache[val];
    if (!promise) {
        promise = $.ajax('/foo/', {
            data: { value: val },
            dataType: 'json'
        });
        cache[val] = promise;
    }
    return promise;
}

$.when(getData('foo')).then(function(resp){
    // do something with the response, which may
    // or may not have been retreived using an
    // XHR request.
});

【讨论】:

  • 我认为这仍然不完美,因为您永远不会在第一次获取缓存时清除/更新缓存。这将使 AJAX 调用不适用于任何更新。
【解决方案3】:

可以使用 deferred 代替互斥锁。这与多个ajax的使用场景基本相同。

MUTEX

var mutex = 2;

setTimeout(function() {
 callback();
}, 800);

setTimeout(function() {
 callback();
}, 500);

function callback() {
 if (--mutex === 0) {
  //run code
 }
}

延期

function timeout(x) {
 var dfd = jQuery.Deferred();
 setTimeout(function() {
  dfd.resolve();
 }, x);
 return dfd.promise();
}

jQuery.when(
timeout(800), timeout(500)).done(function() {
 // run code
});

仅将 Deferred 用作互斥体时,请注意性能影响 (http://jsperf.com/deferred-vs-mutex/2)。尽管 Deferred 提供的便利以及额外的好处非常值得,但在实际(基于用户驱动的事件)使用中,性能影响应该不会很明显。

【讨论】:

  • 找到这个对我来说非常困难。我在一个包含 setInterval 的函数上使用了它,一旦 div 的宽度超过某个数字,它将返回已解决的承诺并自毁。如果我无法解决我的问题,它是用于故障排除和解决方案,但我对此感到欣喜若狂。
【解决方案4】:

这是一个自我推销的答案,但我花了几个月的时间对此进行研究,并在 2012 年旧金山 jQuery 会议上展示了结果。

这是一个免费的演讲视频:

https://www.youtube.com/watch?v=juRtEEsHI9E

【讨论】:

    【解决方案5】:

    我一直致力于的另一个用途是从多个来源获取数据。在下面的示例中,我正在获取现有应用程序中使用的多个独立 JSON 模式对象,以在客户端和 REST 服务器之间进行验证。在这种情况下,我不希望浏览器端应用程序在加载所有模式之前开始加载数据。 $.when.apply().then() 非常适合这个。感谢 Raynos 提供有关使用 then(fn1, fn2) 监视错误情况的指示。

    fetch_sources = function (schema_urls) {
        var fetch_one = function (url) {
                return $.ajax({
                    url: url,
                    data: {},
                    contentType: "application/json; charset=utf-8",
                    dataType: "json",
                });
            }
        return $.map(schema_urls, fetch_one);
    }
    
    var promises = fetch_sources(data['schemas']);
    $.when.apply(null, promises).then(
    
    function () {
        var schemas = $.map(arguments, function (a) {
            return a[0]
        });
        start_application(schemas);
    }, function () {
        console.log("FAIL", this, arguments);
    });     
    

    【讨论】:

      【解决方案6】:

      另一个使用Deferreds 为任何类型的计算(通常是一些性能密集型或长时间运行的任务)实现缓存的示例:

      var ResultsCache = function(computationFunction, cacheKeyGenerator) {
          this._cache = {};
          this._computationFunction = computationFunction;
          if (cacheKeyGenerator)
              this._cacheKeyGenerator = cacheKeyGenerator;
      };
      
      ResultsCache.prototype.compute = function() {
          // try to retrieve computation from cache
          var cacheKey = this._cacheKeyGenerator.apply(this, arguments);
          var promise = this._cache[cacheKey];
      
          // if not yet cached: start computation and store promise in cache 
          if (!promise) {
              var deferred = $.Deferred();
              promise = deferred.promise();
              this._cache[cacheKey] = promise;
      
              // perform the computation
              var args = Array.prototype.slice.call(arguments);
              args.push(deferred.resolve);
              this._computationFunction.apply(null, args);
          }
      
          return promise;
      };
      
      // Default cache key generator (works with Booleans, Strings, Numbers and Dates)
      // You will need to create your own key generator if you work with Arrays etc.
      ResultsCache.prototype._cacheKeyGenerator = function(args) {
          return Array.prototype.slice.call(arguments).join("|");
      };
      

      这里是一个使用这个类来执行一些(模拟重)计算的例子:

      // The addingMachine will add two numbers
      var addingMachine = new ResultsCache(function(a, b, resultHandler) {
          console.log("Performing computation: adding " + a + " and " + b);
          // simulate rather long calculation time by using a 1s timeout
          setTimeout(function() {
              var result = a + b;
              resultHandler(result);
          }, 1000);
      });
      
      addingMachine.compute(2, 4).then(function(result) {
          console.log("result: " + result);
      });
      
      addingMachine.compute(1, 1).then(function(result) {
          console.log("result: " + result);
      });
      
      // cached result will be used
      addingMachine.compute(2, 4).then(function(result) {
          console.log("result: " + result);
      });
      

      相同的底层缓存可用于缓存 Ajax 请求:

      var ajaxCache = new ResultsCache(function(id, resultHandler) {
          console.log("Performing Ajax request for id '" + id + "'");
          $.getJSON('http://jsfiddle.net/echo/jsonp/?callback=?', {value: id}, function(data) {
              resultHandler(data.value);
          });
      });
      
      ajaxCache.compute("anID").then(function(result) {
          console.log("result: " + result);
      });
      
      ajaxCache.compute("anotherID").then(function(result) {
          console.log("result: " + result);
      });
      
      // cached result will be used
      ajaxCache.compute("anID").then(function(result) {
          console.log("result: " + result);
      });
      

      你可以在this jsFiddle中玩上面的代码。

      【讨论】:

        【解决方案7】:

        1) 使用它来确保回调的有序执行:

        var step1 = new Deferred();
        var step2 = new Deferred().done(function() { return step1 });
        var step3 = new Deferred().done(function() { return step2 });
        
        step1.done(function() { alert("Step 1") });
        step2.done(function() { alert("Step 2") });
        step3.done(function() { alert("All done") });
        //now the 3 alerts will also be fired in order of 1,2,3
        //no matter which Deferred gets resolved first.
        
        step2.resolve();
        step3.resolve();
        step1.resolve();
        

        2) 使用它来验证应用的状态:

        var loggedIn = logUserInNow(); //deferred
        var databaseReady = openDatabaseNow(); //deferred
        
        jQuery.when(loggedIn, databaseReady).then(function() {
          //do something
        });
        

        【讨论】:

          【解决方案8】:

          您可以使用延迟对象进行流畅的设计,使其在 webkit 浏览器中运行良好。 Webkit 浏览器将为调整窗口大小的每个像素触发 resize 事件,这与 FF 和 IE 不同,每次调整大小时仅触发一次事件。因此,您无法控制绑定到窗口调整大小事件的函数的执行顺序。这样的事情解决了这个问题:

          var resizeQueue = new $.Deferred(); //new is optional but it sure is descriptive
          resizeQueue.resolve();
          
          function resizeAlgorithm() {
          //some resize code here
          }
          
          $(window).resize(function() {
              resizeQueue.done(resizeAlgorithm);
          });
          

          这将序列化您的代码的执行,使其按照您的预期执行。将对象方法作为回调传递给 deferred 时要小心陷阱。一旦此类方法作为对 deferred 的回调执行,“this”引用将被对 deferred 对象的引用覆盖,并且不再引用该方法所属的对象。

          【讨论】:

          • 这是如何进行序列化的?您已经解决了队列,所以resizeQueue.done(resizeAlgorithm)resizeAlgorithm 完全相同。这完全是个骗局!
          • 当你的 resizeAlgorithm 的代码很复杂时,当你调整窗口大小的每个像素调用该函数时,webkit 中的 JavaScript 实现会失去同步。 Deferred 将您的回调保存在一个队列中,并以 FIFO 顺序执行它们。因此,如果您添加一个“完成”回调并且由于延迟已经解决,它会立即执行,那么在第一个回调仍在执行时添加到延迟的另一个“完成”回调将被添加到队列中并且必须等待第一个回调返回。我希望这能回答你的问题。
          • 浏览器中的JS解释器是单线程的。除非你的 resizeAlgorithm 里面有一些异步代码,否则整个函数应该在下次调用 .done 之前完成操作。
          • @Raynos:我知道这一点,但我试图简单地调用 resizeAlgorithm on resize,它在 webkit 浏览器中提供了一个空白页面,同时在其他浏览器中完美运行。 deferred 解决了这个问题。我没有足够的时间对此进行更深入的研究。可能是 webkit 错误。如果 resizeAlgorithm 有一些异步代码,我认为我的示例中使用的 deferred 不会有帮助。
          • 你不应该使用像油门/去抖动插件benalman.com/projects/jquery-throttle-debounce-plugin 这样的东西来防止你的函数在每次调整大小时触发更多的 tahn。
          【解决方案9】:

          您还可以将它与任何使用 JQuery 的第三方库集成。

          Backbone 就是这样一个库,它实际上将在下一个版本中支持 Deferred。

          【讨论】:

          • 使用read more here 代替on my blog。这是一种更好的做法,可以避免(意外)被垃圾邮件发送。 :)
          【解决方案10】:

          我刚刚在实际代码中使用了 Deferred。在项目jQuery Terminal 中,我有调用用户定义的命令的函数exec(就像他正在输入它并按Enter),我已将Deferreds 添加到API 并使用数组调用exec。像这样:

          terminal.exec('command').then(function() {
             terminal.echo('command finished');
          });
          

          terminal.exec(['command 1', 'command 2', 'command 3']).then(function() {
             terminal.echo('all commands finished');
          });
          

          命令可以运行异步代码,exec需要按顺序调用用户代码。我的第一个 api 使用一对暂停/恢复调用,在新的 API 中,我在用户返回承诺时自动调用它们。所以用户代码可以使用

          return $.get('/some/url');
          

          var d = new $.Deferred();
          setTimeout(function() {
              d.resolve("Hello Deferred"); // resolve value will be echoed
          }, 500);
          return d.promise();
          

          我使用这样的代码:

          exec: function(command, silent, deferred) {
              var d;
              if ($.isArray(command)) {
                  return $.when.apply($, $.map(command, function(command) {
                      return self.exec(command, silent);
                  }));
              }
              // both commands executed here (resume will call Term::exec)
              if (paused) {
                  // delay command multiple time
                  d = deferred || new $.Deferred();
                  dalyed_commands.push([command, silent, d]);
                  return d.promise();
              } else {
                  // commands may return promise from user code
                  // it will resolve exec promise when user promise
                  // is resolved
                  var ret = commands(command, silent, true, deferred);
                  if (!ret) {
                      if (deferred) {
                          deferred.resolve(self);
                          return deferred.promise();
                      } else {
                          d = new $.Deferred();
                          ret = d.promise();
                          ret.resolve();
                      }
                  }
                  return ret;
              }
          },
          

          dalyed_commands 用于恢复函数,该函数使用所有 dalyed_commands 再次调用 exec。

          以及部分命令功能(我已经剥离了不相关的部分)

          function commands(command, silent, exec, deferred) {
          
              var position = lines.length-1;
              // Call user interpreter function
              var result = interpreter.interpreter(command, self);
              // user code can return a promise
              if (result != undefined) {
                  // new API - auto pause/resume when using promises
                  self.pause();
                  return $.when(result).then(function(result) {
                      // don't echo result if user echo something
                      if (result && position === lines.length-1) {
                          display_object(result);
                      }
                      // resolve promise from exec. This will fire
                      // code if used terminal::exec('command').then
                      if (deferred) {
                          deferred.resolve();
                      }
                      self.resume();
                  });
              }
              // this is old API
              // if command call pause - wait until resume
              if (paused) {
                  self.bind('resume.command', function() {
                      // exec with resume/pause in user code
                      if (deferred) {
                          deferred.resolve();
                      }
                      self.unbind('resume.command');
                  });
              } else {
                  // this should not happen
                  if (deferred) {
                      deferred.resolve();
                  }
              }
          }
          

          【讨论】:

            【解决方案11】:

            ehynds 的回答不起作用,因为它缓存了响应数据。它应该缓存同样是 Promise 的 jqXHR。 这是正确的代码:

            var cache = {};
            
            function getData( val ){
            
                // return either the cached value or an
                // jqXHR object (which contains a promise)
                return cache[ val ] || $.ajax('/foo/', {
                    data: { value: val },
                    dataType: 'json',
                    success: function(data, textStatus, jqXHR){
                        cache[ val ] = jqXHR;
                    }
                });
            }
            
            getData('foo').then(function(resp){
                // do something with the response, which may
                // or may not have been retreived using an
                // XHR request.
            });
            

            Julian D. 的答案将是正确的,并且是一个更好的解决方案。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多