[英]jQuery calling multiple setTimeout() sequentially
I'm having a hard time wrapping my mind around jQuery promises. 我很难将我的想法包含在jQuery承诺中。 I've created the following snippet to explore jQuery promises; 我创建了以下片段来探索jQuery的承诺; informed by this StackOverflow entry. 通过此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>
My understanding is that jQuery promises should hold off executing b
and c
while a
is executing and subsequently hold off executing c
while b
is executing. 我的理解是jQuery promise应该在a
执行时阻止执行b
和c
,然后在b
执行时阻止执行c
。
I was expecting the output to be a, b, c
but I'm getting c, b, a
. 我期待输出为a, b, c
但我得到c, b, a
。 Note that I deliberately picked these delays ('a': 5000, 'b': 2500, 'c':0) to illustrate the fact that jQuery promises aren't blocking execution as planned. 请注意,我故意选择了这些延迟('a':5000,'b':2500,'c':0)来说明jQuery promises没有按计划阻止执行。
What am I missing and how should I alter the code to get the expected behavior? 我错过了什么以及如何更改代码以获得预期的行为?
WARNING: this answer only addresses the I was expecting the output to be a, b, c but I'm getting c, b, a.
警告:这个答案只解决了I was expecting the output to be a, b, c but I'm getting c, b, a.
. 。 It not solves the promise issue. 它不能解决承诺问题。
In your code, whatever you hope for (as explained by Michael Geary above), 'a' outputs after 5 seconds, 'b' ouputs after 2.5 seconds and 'c' outputs immediatly. 在您的代码中, 无论您希望如何 (如上面的Michael Geary所解释),'a'在5秒后输出,'b'输出2.5秒后'c'立即输出。
If you want the 'a' to ouptut before the 'c', its waiting time (its timeout ) must be shorter. 如果你想要'a'在'c'之前ouptut,它的等待时间(它的超时 )必须更短。
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);
.as-console-wrapper{max-height:100%!important;top:0;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
By the way, the clearer is your code, the simplier you can debug it, understand it. 顺便说一句,更清晰的是你的代码,你可以更简单地调试它,理解它。 Look for instance: 寻找例子:
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);
.as-console-wrapper{max-height:100%!important;top:0;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Where you are going wrong is in thinking that a promise blocks execution. 你出错的地方在于认为一个承诺会阻止执行。 It doesn't. 它没有。 A promise is just another way to write a callback function; 承诺只是编写回调函数的另一种方式; it doesn't add any magic beyond what JavaScript natively offers. 除了JavaScript本身提供的功能之外,它不会增加任何魔力。 (I'm not addressing async
/ await
here, just "traditional" JavaScript.) All of your code runs to completion before any callbacks get called. (我不是await
这里解决async
/ await
,只是“传统的”JavaScript。)在调用任何回调之前,所有代码都会运行完成。
That's where you're running into trouble. 那就是你遇到麻烦的地方。 Your while
loop runs to completion before any of your queue functions ever get called: while
调用任何队列函数之前, while
循环运行完成 :
while (queue.length > 0) {
d = d.then(queue.shift()); // you don't need the `.done`
}
If you want to have multiple functions get called with timeouts like you're doing, the best way to do it is not to fire all the setTimeout()
calls all in a single gulp as your code does now. 如果你想让多个函数像你正在做的那样超时调用,那么最好的方法就是不要像你现在的代码那样在一个gulp中激活所有的setTimeout()
调用。 Instead, have each of the callback
functions start the next setTimeout()
. 相反,让每个callback
函数启动下一个setTimeout()
。 This way you only have a single timeout pending at any time, and when it fires you start the next one. 这样,您在任何时候只有一个超时挂起,当它触发时,您启动下一个超时。
I wrote a jQuery plugin many years ago for this called slowEach
. 多年前我写了一个jQuery插件,名为slowEach
。 (It doesn't really depend on jQuery and the same technique would work for non-jQuery code too.) (它实际上并不依赖于jQuery,同样的技术也适用于非jQuery代码。)
The code doesn't use promises - it predates them by several years - but the principle is the same whether you use promises or traditional callbacks: start with a single setTimeout()
, and then when its callback function gets called, start the next setTimeout()
. 代码不使用promises - 它早于它们几年 - 但无论你使用promises还是传统的回调,原理都是一样的:从一个setTimeout()
,然后当它的回调函数被调用时,启动下一个setTimeout()
。 This sequences the timeouts in the manner you were probably hoping the simple while
loop would do. 这可以按照您希望简单的while
循环执行的方式对超时进行排序。
The original slowEach()
code can be found in this answer . 原始的slowEach()
代码可以在这个答案中找到。 Several people have made improvements to the code since then. 从那时起,有几个人对代码进行了改进 。 In particular, here is a version that adds an onCompletion
callback so you get a different callback when the entire array has been processed. 特别是, 这是一个添加onCompletion
回调的版本 ,因此在处理整个数组时会得到不同的回调。
This code doesn't use promises, but more importantly, it works . 此代码不使用promises,但更重要的是,它可以使用 。 :-) It would be an interesting exercise to adapt the code to use promises. :-)这将是一个有趣的练习,使代码适应使用promises。
But again, don't assume that JavaScript will wait for a promise to be done before it executes the next line of code. 但同样,不要认为JavaScript会在执行下一行代码之前等待承诺完成。 Unless you're using async
/ await
, whatever ordinary loop you're running - while
, for
, whatever - will always run to completion before any of the callbacks - ordinary callbacks or promises - get run. 除非你使用async
/ await
,你正在运行的任何普通环路- while
, for
,不管是谁-普通回调或承诺- -获得运行将之前的任何回调的始终运行完成。
I suggest to use this jQuery extension to make sequential execution in a deferred queue. 我建议使用这个jQuery扩展在延迟队列中进行顺序执行。
You can use your list of timeout function as input of deferQueue, and execute them inside a main function (callable). 您可以使用超时函数列表作为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 : $(<iterable>).deferQueue(<function(element)>,<options>) 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>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.