jQuery顺序调用多个setTimeout()

时间:2017-10-29 04:28:42

标签: javascript jquery promise

我很难围绕jQuery承诺。我创建了以下片段来探索jQuery的承诺;通过此StackOverflow条目得知。

let q = [
  function () { setTimeout(function () { console.log("a - " + Date.now()); }, 5000); },
  function () { setTimeout(function () { console.log("b - " + Date.now()); }, 2500); },
  function () { setTimeout(function () { console.log("c - " + Date.now()); }, 0); }
];
    
function SerialCall(queue) {
  var d = $.Deferred().resolve();
   while (queue.length > 0) {
     d = d.then(queue.shift()); // you don't need the `.done`
   }
}

SerialCall(q);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

我的理解是jQuery promises应该在执行b时阻止执行ca,并在c执行时暂停执行b

我希望输出为a, b, c,但我得到c, b, a。请注意,我故意选择了这些延迟('a':5000,'b':2500,'c':0)来说明jQuery promises没有按计划阻止执行。

我错过了什么以及如何更改代码以获得预期的行为?

3 个答案:

答案 0 :(得分:2)

警告:此答案仅针对I was expecting the output to be a, b, c but I'm getting c, b, a.。它不能解决承诺问题。

在您的代码中,您希望的任何内容(如上文Michael Geary所述),&#39; a&#39; 5秒后输出,&#39; b&#39; 2.5秒后输出&#39; c&#39;立即输出。

如果你想要&#39; a&#39;在&#39; c&#39;之前ouptut,其等待时间(超时)必须更短。

&#13;
&#13;
let queue = [
    function () { 
      let waitingTime = 0 ;
      setTimeout(function () { console.log("a - " + Date.now()); }, waitingTime); },
    function () { 
      let waitingTime = 2500 ;
      setTimeout(function () { console.log("b - " + Date.now()); }, waitingTime); },
    function () { 
      let waitingTime = 5000 ;
      setTimeout(function () { console.log("c - " + Date.now()); }, waitingTime); }
];

function SerialCall(queue) {
    var d = $.Deferred().resolve();
    while (queue.length > 0) {
        d = d.then(queue.shift()); // you don't need the `.done`
    }
}

SerialCall(queue);
&#13;
.as-console-wrapper{max-height:100%!important;top:0;}
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
&#13;
&#13;
&#13;

顺便说一句,更清晰的是你的代码,你可以更简单地调试它,理解它。寻找例子:

&#13;
&#13;
let queue = [
    function () { waitForMe('a', 0)    },
    function () { waitForMe('b', 2500) },
    function () { waitForMe('c', 5000) }
];

function SerialCall(queue) {
    var d = $.Deferred().resolve();
    while (queue.length > 0) {
        d = d.then(queue.shift()); // you don't need the `.done`
    }
}

function waitForMe(letter, someTime) {
  return setTimeout(function () { console.log(letter +" - " + Date.now()); }, someTime)
}

SerialCall(queue);
&#13;
.as-console-wrapper{max-height:100%!important;top:0;}
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
&#13;
&#13;
&#13;

答案 1 :(得分:1)

你出错的地方在于认为一个承诺会阻止执行。它没有。承诺只是编写回调函数的另一种方式;除了JavaScript本身提供的功能之外,它并没有增加任何魔力。 (我不是在这里处理async / await,而是传统的&#34; JavaScript。)在调用任何回调之前,所有代码都会运行完毕。

你遇到麻烦的地方。在调用任何队列函数之前,while循环运行完成

while (queue.length > 0) {
    d = d.then(queue.shift()); // you don't need the `.done`
}

如果你想让多个函数像你一样被执行超时调用,那么最好的方法就是不要像你现在的代码那样在一个gulp中激活所有setTimeout()个调用。相反,让每个callback函数启动下一个setTimeout()。这样,您在任何时候只有一个超时挂起,当它触发时,您启动下一个超时。

我多年前写了一个jQuery插件,名为slowEach。 (它并不真正依赖于jQuery,同样的技术也适用于非jQuery代码。)

代码没有使用promises - 它早于它们几年 - 但无论你使用promises还是传统的回调,原理都是一样的:从单个setTimeout()开始,然后在它的回调函数时被叫,开始下一个setTimeout()。这可以按照您希望简单while循环的方式对超时进行排序。

原始slowEach()代码可在this answer中找到。从那以后,有几个人做了improvements to the code。特别是here is a version添加onCompletion回调,以便在处理整个数组时获得不同的回调。

此代码不使用promises,但更重要的是,可以使用。 :-)调整代码以使用promises是一个有趣的练习。

但是,不要再假设JavaScript会在执行下一行代码之前等待承诺完成。除非您正在使用async / await,否则您正在运行的普通循环 - whilefor,无论如何 - 将始终在<<}之前完成em>任何的回调 - 普通的回调或承诺 - 运行。

答案 2 :(得分:0)

我建议使用此jQuery扩展在延迟队列中进行顺序执行。

您可以使用超时函数列表作为deferQueue的输入,并在main函数(可调用)中执行它们。

$.fn.deferQueue = function(callable, options){
    options = Object(options);
		var it = (this.get() || [])[Symbol.iterator]();
		var stop = false, cond = null;
		var self = this;
		this.stop = function(){ stop=true; };
		this.end = function(_cond){ cond = _cond };
		var tid = 0;
		var iterate = function(){
			if(tid) clearTimeout(tid);
			var o = it.next();
			if(cond instanceof Function && cond(o.value)) return;
			if(o.done || stop) return;
			var d = callable.call(self, o.value);
			if(options.timeout) tid = setTimeout(function(){ d.reject('timeout'); }, options.timeout);
			if(options.success) d.done(options.success);
			if(options.fail) d.fail(options.fail);
			d[options.iterate || 'always'](iterate);
		}
		iterate();
		return this;
	}
  
  function log(text){
  	console.log('Log: '+text);
  }
  function error(text){
  	console.log('Error: '+text);
  }
  
  let q = [
  function (D) { setTimeout(function () { console.log("a - " + Date.now()); D.resolve('function 1'); }, 5000); },
  function (D) { setTimeout(function () { console.log("b - " + Date.now()); D.resolve('function 2') }, 2500); },
  function (D) { setTimeout(function () { console.log("c - " + Date.now()); D.resolve('function 3') }, 0); },
'https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png',
'https://unreachabe_domain/image.png',
'https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png',
null,
'https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png'
];
  
  $(function(){      	
    var
      display_timeout = parseInt($('#container').data('display-timeout')),
      loading_timeout = parseInt($('#container').data('loading-timeout'));
      
    $(q).deferQueue(function(e){
    	var D = $.Deferred();
      if(typeof(e) == 'string') $('<img/>').attr('src',e).on('load',function(){ setTimeout(function(){ D.resolve(e); },display_timeout)}) 
        .on('error', function(){ D.reject(e) })
      	.appendTo('#container');
      else if(e instanceof Function) e(D);
      D.done(log).fail(error);
      return D;
    },{iterate:'always',timeout:loading_timeout}).end(function(e){ return e===null; });
  
  })
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<pre>
  usage : $(&lt;iterable&gt;).deferQueue(&lt;function(element)&gt;,&lt;options&gt;)
  
  options:
      timeout: int(seconds)
      iterate: string(always | done | fail)
      success: function
      fail: function
</pre>
<div id="container" data-display-timeout="1000" data-loading-timeout="3000">

</div>