使用异步子例程对一组函数进行计时

Timing a set of functions with asyncronous subroutines

本文关键字:函数 一组 异步 子例程      更新时间:2023-09-26

我有两个通过setInterval定期调用的函数。目标是将函数 B 推迟到函数 A 完成(反之亦然)。目前,函数A将启动,完成其部分子例程,但在函数B开始之前不会到达终点。

我尝试将函数 B 作为函数 A 的参数传递。我不确定这是否足以创建回调。我也尝试了jQuery的$.when(setInterval(functionA, 10000)).then(setInterval(functionB, 5000))

如何要求 JavaScript 等待函数/代码块完成?提前谢谢你。


编辑:下面的代码与我的原始代码非常相似。很抱歉不简洁。

函数A,getFruits():有一个远程JSON会自行更改(fruits.json)。 getFruits() 做两件事:1) 它清空一个数组,[allFruits](以防万一);2)它将远程JSON中当前所有水果的名称添加到[allFruits]。现在,[allFruits] 是远程 JSON 的实例化副本。在这个问题之前,我只在启动时调用了一次getFruits();换句话说,我没有将setInterval用于getFruits()。

函数 B, checkFruits()

:现在 checkFruits() 定期 ( setInterval(checkFruits, 5000) ) 将 [allFruits] 与远程版本进行比较。如果有任何水果被添加到远程版本中,checkFruit 会在 [allFruits] 后附加这些水果的名称;它还运行有用的代码(即将新名称推送到数组 [queue])。

对于此实现,创建一个初始列表非常重要,以便只有新的(启动后)水果才会触发 checkFruits() 的有用代码。此外,重要的是只在会话中添加(切勿减去)[allFruits]中的名称。这是为了防止新水果在每个会话中多次触发有用的代码。

问题:现在我想使getFruits()(函数A)周期性。因为getFruits()清空了[allFruits],它将允许建立的名称再次触发有用的代码(但只在getFruits()的调用之间触发一次)。但是,当我使用 setInterval(getFruits, 10000) 时,有时(在本例中,总是)getFruits() 与 checkFruits() 重叠。当这种情况发生时,我注意到在 checkFruits() 启动之前只有一部分 getFruits() 完成。console.log() 消息按以下顺序显示:'getFruits() start:''checkFruits():''getFruits() end:' 。此外,我的有用代码是在getFruits()完成之前运行的(这是真正不需要的),并且[allFruits]得到重复。如果 getFruits() 在 checkFruits() 跳入之前完全完成,则不会发生这种情况。

debugging = true;
var debug = function() {
	if (debugging){
		console.log.apply(console, arguments)
	};
}
var allFruits = [];
var queue = [];
var getFruits = function() {
	allFruits = []; // Empty the list
	debug('getFruits() start:', 'allFruits =', allFruits, 'queue =', queue);
	$.ajax({
		url: 'fruits.json',
		dataType: 'json',
		success: function(data) {
			data.fruits.forEach(function(element) {
				allFruits.push(element.name);
			});
			debug('getFruits() end:', 'data =', data, 'allFruits =', allFruits, 'queue =', queue);
		},
	});
}
var checkFruits = function() {
	$.ajax({
		url: 'fruits.json',
		dataType: 'json',
		success: function(data) {
			data.fruits.forEach(function(element) {
				if (allFruits.indexOf(element.name) === -1) {
					queue.push(['fruit', element.name]);
					allFruits.push(element.name);
				}
			});
			debug('checkFruits():', 'data =', data, 'allFruits =', allFruits, 'queue =', queue);	
		}
	});
}
getFruits();
setInterval(checkFruits, 5000);
// setInterval(getFruits, 10000); // When I try this, checkFruits() does not wait for getFruits() to finish.

我的实际远程资源的类比是fruits.json.fruits.json可以简单地如下: {"fruits":[{"name":"apple","color":"red"},{"name":"banana","color":"yellow"},{"name":"tangerine","color":"orange"}]}

同样,实际的远程 JSON 会独立更改。

这里有两个方法,每个方法都做异步的事情。这里有一些关于这意味着什么的很好的堆栈溢出帖子。

易于理解的"异步事件"定义?

异步编程是否意味着多线程?

JavaScript 函数是异步的吗?

我们不知道异步调用需要多长时间才能完成。在您的情况下,AJAX 请求最多可能需要几秒钟,具体取决于网络速度,因此无论何时执行这些方法中的每一个,您都无法知道哪个方法将首先完成。那怎么办?通常,当您编写/使用异步方法(如 $.ajax)时,您会给它一个回调,该回调将在异步工作完成后执行。您已经以success回调的形式完成了此操作。这是个好消息。success回调是同步的(注意缺少的 a)。这意味着在请求完成时需要运行的success回调中的"有用代码"将在执行其他success回调中的"其他有用代码"之前完成(只要它都不是异步的)。无论哪个请求先完成,这都有效。每个success回调将始终等待另一个回调。所以我认为让你感到困惑的是你的调试语句。如果将以下语句添加到代码中,则执行流可能更有意义:

debugging = true;
var debug = function() {
  if (debugging) {
    console.log.apply(console, arguments)
  };
}
var allFruits = [];
var queue = [];
var getFruits = function() {
  debug("getFruits: make request");
  $.ajax({
    url: 'fruits.json',
    dataType: 'json',
    success: function(data) {
      debug("getFruits: start processing");
      allFruits = []; // Empty the list
      data.fruits.forEach(function(element) {
        allFruits.push(element.name);
      });
      debug('getFruits: finished processing');
    },
  });
  debug("getFruits: request sent, now we wait for a response.");
}
var checkFruits = function() {
  debug("checkFruits: make request");
  $.ajax({
    url: 'fruits.json',
    dataType: 'json',
    success: function(data) {
      debug("checkFruits: start processing");
      data.fruits.forEach(function(element) {
        if (allFruits.indexOf(element.name) === -1) {
          queue.push(['fruit', element.name]);
          allFruits.push(element.name);
        }
      });
      debug("checkFruits: finished processing");
    }
  });
  debug("checkFruits: request sent, now we wait for a response.");
}
getFruits();
setInterval(checkFruits, 5000);
// setInterval(getFruits, 10000); // When I try this, checkFruits() does not wait for getFruits() to finish.

经过思考,我相信事情可能没有按预期运行的唯一原因是因为您在回调之外清空了allFruits数组。如果你像我一样移动它,我认为一切都应该正常。

现在,我不知道为什么您需要重新初始化数据,因为每次发出请求时都会获得最新信息,但让我们继续前进。由于两种方法发出相同的请求,因此可以将其合并为单个方法。无需复制代码;)。由于您所有示例的getFruits运行速度都是checkFruits的两倍,因此我们可以轻松地添加一个计数器来完成相同的事件序列,如下所示:

    debugging = true;
    var debug = function() {
      if (debugging) {
        console.log.apply(console, arguments)
      };
    }
    var allFruits = [];
    var queue = [];
    var count = 0;
    var doOneThing = function(data) {
       //do stuff
    }
    
    var doAnotherThing= function(data) {
       //do other stuff
    }
    var requestFruits = function() {
      $.ajax({
        url: 'fruits.json',
        dataType: 'json',
        success: function(data) {
          // if count is even...or, do this every other time.
          if (count % 2 === 0) {
            count++;
            doOneThing(data);
          }
          // do this everytime
          doAnotherThing(data);
        },
      });
    }
    setInterval(requestFruits, 5000);

希望这有帮助。干杯。

您的最后一个代码示例首先执行 setInterval(functionA),当设置函数 A 的延迟执行时,执行 setInterval(functionB),这意味着 B 将在该行执行后 +- 5 秒调用,而函数 A 称为 +- 10 秒。

编辑以反映您的其他信息:

setInterval(function(){
   functionA();
   functionB();
}, 10000)
setTimeout(function(){
    setInterval(functionB, 10000)
}, 5000)

这是一个粗略的答案。我觉得回调可以实现这一点,但我不确定如何编码它们,尤其是涉及setInterval.

我创建了两个全局变量,getFruitsIsBusy = falsecheckFruitsIsBusy = false 。我为 getFruits() 和 checkFruits() 创建了一个 IF。这是getFruits():

var getFruits = function() {
	if (checkFruitsIsBusy) { // New
		setTimeout(getFruits, 100); // New
		return; // New
	
	} else { // New
		getFruitsIsBusy = true // New
		
		allFruits = []; // Empty the list
		debug('getFruits() start:', 'allFruits =', allFruits, 'queue =', queue);
		$.ajax({
			url: 'fruits.json',
			dataType: 'json',
			success: function(data) {
				data.fruits.forEach(function(element) {
					allFruits.push(element.name);
				});
				getFruitsIsBusy = false // New; in the success function
				debug('getFruits() end:', 'data =', data, 'allFruits =', allFruits, 'queue =', queue)
			},
		});
	}
}

如果也对 checkFruits() 使用此范例,似乎两个函数都将等待对方完成。

基于对两个函数(A 和 B)时序的分析,考虑以下解决方案(Chionglo,2016 年):

  1. 保留函数 A 和函数 B 的状态信息。每个函数的状态应在各自的函数中设置。
  2. 为函数 A 和函数 B 中的每个函数创建一个包装函数。包装函数调用相应的函数,然后检查相应函数的状态。

    一个。签入包装函数 A:如果函数 A 已达到最终状态,则清除与包装函数 A 关联的间隔,并为包装函数 B 安排间隔。

    二.签入包装函数 B:如果函数 B 已达到其最终状态,则清除与包装函数 B 关联的间隔。

  3. 要开始该过程,请为包装函数 A 安排一个间隔。

示例代码:

var ac = Math.round(4*Math.random())+4;
var bc = Math.round(6*Math.random())+6;
var ai;
var Astate = false;
var Bstate = false;
function A() {
  // Do your thing for A here.
  // The following changes the “state of A” and then determines if the final state has been reached.
  ac -= 1;
  if (ac<1) Astate = true;
  else Astate = false;
}
function B() {
  // Do your thing for B here.
  // The following changes the “state of B” and then determines if the final state has been reached.
  bc -= 1;
  if (bc<1) Bstate = true;
  else Bstate = false;
}
ai = setInterval("processA()", 1000);
function processA() {
  A();
  if (Astate) {
    clearInterval(ai);
    ai = setInterval("processB()", 500);
  }
}
function processB() {
  B();
  if (Bstate) {
    clearInterval(ai);
    ai = undefined;
  }
}

参考

Chionglo, J. F. (2016).对一组函数进行计时的分析。可在 http://www.aespen.ca/AEnswers/1458200332.pdf 购买。