确定javascript异步操作的结束

时间:2011-07-27 22:35:07

标签: javascript asynchronous

如果我有一个传递了这个功能的功能:

function(work) {
   work(10);
   work(20);
   work(30);
}

(可以有任意数量的work来电,其中包含任意数字。)

work表现出一些异步活动 - 比如,对于这个例子,它只是一个timeout。我完全可以控制work在完成此操作时所做的事情(事实上,它的定义一般来说)。

确定何时完成对work的所有通话的最佳方式是什么?


当前方法在调用工作时递增计数器,在完成时递减计数器,并在计数器为0时触发all work done事件(在每次递减后检查)。但是,我担心这可能是某种竞争条件。如果情况并非如此,请展示我的原因,这将是一个很好的答案。

3 个答案:

答案 0 :(得分:2)

没有竞争条件。每个请求在完成时都要求执行递减(总是!包括http失败,这很容易忘记)。但这可以通过包装你的调用以更封装的方式处理。

未经测试,但这是要点(我已经实现了一个对象而不是一个计数器,所以理论上你可以扩展它以获得关于特定请求的更细粒度的查询):

var ajaxWrapper = (function() {
   var id = 0, calls = {};
   return {
      makeRequest: function() {
         $.post.apply($, arguments); // for example
         calls[id] = true;
         return id++;
      },
      finishRequest: function(id) {
         delete calls[id];
      },
      isAllDone: function(){
         var prop;
         for(prop in calls) {
            if(calls.hasOwnProperty(prop)) {return false;}
         }
         return true;
      }
   };
})();

用法:

而不是$.post("url", ... function(){ /*success*/ } ... );我们会做

var requestId;
requestId = ajaxWrapper.makeRequest("url", ...
   function(){ /*success*/ ajaxWrapper.finishRequest(requestId); } ... );

如果您想要更复杂,可以在包装器中自己添加finishRequest的调用,因此使用几乎完全透明:

 ajaxWrapper.makeRequest("url", ... function(){ /*success*/ } ... );

答案 1 :(得分:2)

您可以通过多种方式编写此程序,但使用计数器的简单方法可以正常工作。

要记住的重要一点是,这将起作用的原因是因为 Javascript在单个线程中执行。所有浏览器和node.js AFAIK都是如此。

基于下面的深思熟虑的评论,解决方案可行,因为JS事件循环将按以下顺序执行函数:

  1. 功能(工作)
  2. 工作(10)
  3. 计数器++
  4. 启动异步功能
  5. 工作(20)
  6. 计数器++
  7. 启动异步功能
  8. 工作(30)
  9. 计数器++
  10. 启动异步功能
  11. - 退回到事件循环 -
  12. 异步功能完成
  13. 计数器 -
  14. - 退回到事件循环 -
  15. 异步功能完成
  16. 计数器 -
  17. - 退回到事件循环 -
  18. 异步功能完成
  19. 计数器 -
  20. 计数器为0,因此您可以解除工作完成消息
  21. - 退回到事件循环 -

答案 2 :(得分:1)

我有一个after效用函数。

var after = function _after(count, f) {
  var c = 0, results = [];
  return function _callback() {
    switch (arguments.length) {
      case 0: results.push(null); break;
      case 1: results.push(arguments[0]); break;
      default: results.push(Array.prototype.slice.call(arguments)); break;
    }
    if (++c === count) {
      f.apply(this, results);
    }
  };
};

以下代码可行。因为javascript是单线程的。

function doWork(work) {
  work(10);
  work(20);
  work(30);
}

WorkHandler(doWork);

function WorkHandler(cb) {
  var counter = 0,
      finish;
  cb(function _work(item) {
    counter++;
    // somethingAsync calls `finish` when it's finished
    somethingAsync(item, function _cb() {
      finish()
    });
  });
  finish = after(counter, function() {
    console.log('work finished');
  });
};

我想我应该解释一下。

我们将有效的函数传递给workhandler。

工作处理程序调用它并传入工作。

执行工作的函数调用多次递增计数器

由于有效的函数不是异步的(非常重要),我们可以在完成后定义完成函数。

正在完成的异步工作无法在当前同步工作块(整个工作处理程序的执行)完成之前完成(并调用未定义的完成函数)。

这意味着在整个workhandler完成后(并且设置了变量finish),异步工作作业将开始结束并调用finish。只有当所有人都呼叫完成后,回调才会发送到after