【问题标题】:jQuery calling multiple setTimeout() sequentiallyjQuery 依次调用多个 setTimeout()
【发布时间】:2017-10-29 04:28:41
【问题描述】:

我很难将自己的想法围绕在 jQuery 承诺上。我创建了以下 sn-p 来探索 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 承诺应该在 a 正在执行时推迟执行 bc,然后在 b 正在执行时推迟执行 c

我期待输出为a, b, c,但我得到c, b, a。请注意,我特意选择了这些延迟('a': 5000, 'b': 2500, 'c':0)来说明 jQuery 承诺没有按计划阻止执行的事实。

我缺少什么以及我应该如何更改代码以获得预期的行为?

【问题讨论】:

    标签: javascript jquery promise


    【解决方案1】:

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

    在您的代码中,无论您希望什么(如上面 Michael Geary 所解释的那样),'a' 会在 5 秒后输出,'b' 会在 2.5 秒后输出,'c' 会立即输出。

    如果你想让'a'在'c'之前输出,它的等待时间(它的超时)必须更短。

    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>

    顺便说一句,您的代码越清晰,您就越容易调试和理解它。例如:

    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>

    【讨论】:

    • 感谢您的回复,我特意选择了这些延迟来说明 jQuery 承诺没有按计划阻止执行的事实。
    • Philcare,我正试图围绕 jQuery 承诺。在我的示例中,简单的 setTimeout() 调用充当我希望按顺序运行的更复杂函数的代理。很遗憾,您的回答无助于应对这一挑战。
    • 根据我的问题:“我的理解是 jQuery 承诺应该在 a 正在执行时推迟执行 bc,然后在 b 正在执行时推迟执行 c 。”我应该如何更改代码以获得预期的行为?
    【解决方案2】:

    您的错误之处在于认为 promise 会阻止执行。它没有。 Promise 只是编写回调函数的另一种方式;除了 JavaScript 本身提供的功能之外,它并没有增加任何魔力。 (我在这里不是针对async/await,只是“传统”JavaScript。)在调用任何回调之前,您的所有代码都会运行完成。

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

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

    如果您想像现在这样在超时的情况下调用多个函数,最好的方法是不要像现在的代码那样在一次 gulp 中触发所有 setTimeout() 调用。相反,让每个callback 函数启动下一个setTimeout()。这样,您在任何时候都只有一个等待超时,当它触发时,您将启动下一个。

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

    代码没有使用 Promise——它比它们早了几年——但无论你使用 Promise 还是传统的回调,原则都是一样的:从单个 setTimeout() 开始,然后当它的回调函数被调用时,开始下一个setTimeout()。这会以您可能希望简单的while 循环执行的方式对超时进行排序。

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

    这段代码没有使用 Promise,但更重要的是,它工作。 :-) 调整代码以使用 Promise 将是一个有趣的练习。

    但同样,不要假设 JavaScript 会在执行下一行代码之前等待承诺完成。除非您使用 async/await,否则无论您正在运行什么普通循环 - whilefor 等等 - 总是会在 任何 回调之前运行完成 - 普通回调或承诺 - 运行。

    【讨论】:

      【解决方案3】:

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

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

      $.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>

      【讨论】:

      • 不鼓励仅使用代码回答,因为它们没有为未来的读者提供太多信息,请对您所写的内容提供一些解释
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-07-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-04
      相关资源
      最近更新 更多