Si tengo una función que ha pasado esta función:

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

(Puede haber cualquier número de llamadas work con cualquier número en ellas).

work realiza alguna actividad asincrónica; digamos, para este ejemplo, simplemente es un timeout. Tengo control total sobre lo que work hace al completar esta operación (y, de hecho, su definición en general).

¿Cuál es la mejor manera de determinar cuándo se realizan todas las llamadas a work?


Mi método actual incrementa un contador cuando se llama al trabajo y lo disminuye cuando se completa, y dispara el evento all work done cuando el contador es 0 (esto se verifica después de cada disminución). Sin embargo, me preocupa que esto podría ser una condición de carrera de algún tipo. Si ese no es el caso, muestre mi por qué y esa sería una gran respuesta.

3
Aaron Yodaiken 28 jul. 2011 a las 02:35

3 respuestas

La mejor respuesta

Hay muchas maneras de escribir este programa, pero su técnica simple de usar un contador funcionará bien.

Lo importante para recordar, la razón por la que esto funcionará, es porque Javascript se ejecuta en un solo hilo . Esto es cierto para todos los navegadores y node.js AFAIK.

Según los comentarios reflexivos a continuación, la solución funciona porque el bucle de eventos JS ejecutará las funciones en un orden como:

  1. función (trabajo)
  2. trabajo (10)
  3. contador ++
  4. Iniciar función asíncrona
  5. trabajo (20)
  6. contador ++
  7. Iniciar función asíncrona
  8. trabajo (30)
  9. contador ++
  10. Iniciar función asíncrona
  11. - volver al bucle de eventos -
  12. La función asincrónica se completa
  13. mostrador--
  14. - volver al bucle de eventos -
  15. La función asincrónica se completa
  16. mostrador--
  17. - volver al bucle de eventos -
  18. La función asincrónica se completa
  19. mostrador--
  20. El contador es 0, así que disparas tu mensaje de trabajo realizado
  21. - volver al bucle de eventos -
2
Mike 27 jul. 2011 a las 23:46

No hay condición de carrera. Existe el requisito adicional de cada solicitud realizada para realizar una disminución cuando finaliza ( siempre! incluso en caso de error http, que es fácil de olvidar). Pero eso puede manejarse de una manera más encapsulada envolviendo sus llamadas.

No probado, pero esta es la esencia (he implementado un objeto en lugar de un contador, por lo que teóricamente puede extender esto para tener consultas más granulares sobre solicitudes específicas):

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;
      }
   };
})();

Uso:

En lugar de $.post("url", ... function(){ /*success*/ } ... ); haremos

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

Si quisieras ser aún más sofisticado, puedes agregar las llamadas a finishRequest a ti mismo dentro del contenedor, por lo que el uso sería casi completamente transparente:

 ajaxWrapper.makeRequest("url", ... function(){ /*success*/ } ... );
2
davin 27 jul. 2011 a las 22:57

Tengo una función de utilidad 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);
    }
  };
};

El siguiente código a continuación simplemente funcionaría. Porque javascript es de un solo subproceso.

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');
  });
};

Supongo que debería explicarlo.

Pasamos la función que funciona al manipulador de trabajo.

El controlador de trabajo lo llama y pasa al trabajo.

La función que funciona, las llamadas funcionan varias veces incrementando el contador

Como la función que funciona no es asíncrona (muy importante), podemos definir la función de finalización una vez que ha finalizado.

El trabajo asincrónico que se está realizando no puede finalizar (y llamar a la función de finalización indefinida) antes de que finalice el bloque de trabajo síncrono actual (la ejecución de todo el controlador de trabajo).

Esto significa que después de que todo el controlador de trabajo haya finalizado (y se haya configurado el acabado variable), los trabajos de trabajo asincrónicos comenzarán a terminar y finalizarán las llamadas. Solo una vez que todos hayan llamado al final, la devolución de llamada se enviará a after.

1
Raynos 18 ago. 2011 a las 21:02