2010-12-21 10 views
5

¿Podría explicarme cómo escribir un control de flujo realmente básico en JavaScript? Gracias.Control de flujo básico en JavaScript

flow([ 

    function(callback) { /* do something */ callback(); /* run next function */ }, 
    function(callback) { /* do something */ callback(); /* run next function */ }, 
    function(callback) { /* do something */ callback(); /* run next function */ }, 
    function(callback) { /* do something */ callback(); } 

], function() { 

    alert("Done."); 

}); 
+3

... ¿podría explicar con más detalle lo que quiere hacer? – deceze

+0

Me gustaría escribir este control de flujo, pero no sé cómo. Busqué mucho código, pero no lo conseguí. –

+0

¿Las funciones son potencialmente asincrónicas? – slebetman

Respuesta

0
(function(){ 
    function a(cb) { alert('hi'); cb(); } 
    function b(cb) { alert('there'); cb(); } 
    function c(cb) { alert('replace alert with console.log for ease'); cb(); } 
    var done = function() { alert('done'); } 
    a(b(c(done))); 
})() 
0
// callback is a global function, I assume 

function flow(funcArr, funcEnd) {  
    for (var i = 0; i < funcArr.length; i++) { 
     funcArr[i](callback); 
    }  
    funcEnd(); 
} 

Esto iría en todas esas funciones.

+0

Pero no en el orden especificado si son asincrónicos. – slebetman

+0

Sí. Me gustaría ejecutar esas funciones en el orden especificado. –

+0

@slebetman La forma en que mi código funciona es que todas las funciones son sincrónicas. –

7

¿Funcionaría algo como esto?

function flow(fns, last) { 
    var f = last; 
    for (var i = fns.length - 1; i >= 0; i--) 
     f = makefunc(fns[i], f); 
    f(); 
} 

function makefunc(f, g) { 
    return function() { f(g) } 
} 
+0

Esta respuesta es corta, elegante y correcta. –

+0

¿Qué hace var f = last achieve? – qwertymk

+1

@qwertymk: 'f' se inicializa con' last' y se construye desde allí. Primero, es 'last'. Luego, es 'makefunc (fns [n-1], last)', seguido de 'makefunc (fns [n-2], makefunc (fns [n-1], last))', etc. Es como construir un [cons list] (http://en.wikipedia.org/wiki/Cons), donde 'makefunc' es" contras ", y' last' es "nil". –

1

He estado haciendo bastante de eso en un proyecto reciente. He escrito un código para ayudar a administrarlo. Aquí está el código. Usted pasa la función bundledAsync a un objeto con un parámetro "calls" y un parámetro "bundleCallback". El parámetro de llamadas es una matriz de objetos que representa la función que desea llamar. En el parámetro fn, almacena una referencia al parámetro real. En el parámetro "args", almacenas tus argumentos. El último argumento de cada una de las funciones que pasa debe ser una devolución de llamada, que debe ser llamada.

Me siento miserable al documentar mi código y hacer que sea útil para otros, pero esto es realmente realmente útil para mí. Sospecho que alguien más ha escrito algo similar, tal vez debidamente documentado. Si no puede encontrarlo, y necesita ayuda para resolverlo, hágamelo saber.

/** 
    This is a way to submit multiple async calls, and be notified when they've all finished 

    <pre> 

    NameSpace.bundledAsync({ 
    calls:[ 
     { 
     fn: service.getGroups, 
     args: [ 
      function(listsArg){ 
      listsSummary = listsArg; 
      } 
     ], 
     calls: function(){return UNAB.Util.makeArray(listsSummary, function(list){ 
      return { 
      fn: service.getGroup, 
      args: [list.id, function(resp){listsDetail.push(resp)}] 
      } 
     })} 
     } 
    ], 
    bundleCallback: function(){ 
     callback(listsDetail) 
    } 
    }); 

    </pre> 

    @class bundledAsync 
    @static 

*/ 

NameSpace.bundledAsync = function(options){ 

    var callbacksLeft = 0; 
    var calls = $.grep(options.calls, function(call){return call}); 


    if(options.hasOwnProperty("bundleCallback") && typeof options.bundleCallback != "function"){ 
     throw new Error("bundleCallback, passed to bundledAsync, must be a function."); 
    } 

    if(options.chain){ // if this is true, sibling calls will run in succession, not in parallel 
     calls.reverse(); 
     var newCalls = [calls.pop()]; 
     var lastCall = newCalls[0]; 
     while(calls.length > 0){ 
     if(lastCall.calls){ 
      throw new Error("You can't nest calls if you're in chain mode"); 
     } 
     lastCall.calls = [calls.pop()]; 
     lastCall = lastCall.calls[0]; 
     } 
     calls = newCalls; 
    } 

    var decrimentCallbacksLeft = function(){ 
     if(options.name){ 
     // log.debug("Starting decrimentCallbacksLeft for: " + options.name + ". Decrimenting callbacksLeft to: " + (callbacksLeft - 1)); 
     } 
     if(--callbacksLeft == 0 && options.bundleCallback){ 
     // log.debug("No callbacks left. Calling bundleCallback for name: " + options.name); 
     options.bundleCallback(); 
     } 
    } 

    var doCalls = function(callsToDo){ 

     if(typeof callsToDo == "function"){ 
     callsToDo = callsToDo(); 
     }else{ 
     callsToDo = $.extend(true, [], callsToDo);// in case we want to reuse the calls 
     } 

     // right away, return if the calls are empty 
     // check to make sure callbacksLeft == 0, because 
     // we may be dealing with nested calls 
     if(callsToDo.length ==0 && callbacksLeft == 0){ 
     // log.debug("callsToDo is empty, so call the callback right away."); 
     options.bundleCallback(); 
     return null; 
     } 

     callbacksLeft += callsToDo.length; 
     $.each(callsToDo, function(index, call){ 
     var numFns = 0; 
     // // Look through the args searching for functions. 
     // // When one is found, wrap it with our own function. 
     // // This assumes that each function has exactly one 
     // // callback, and that each callback is called exactly once 
     // args can be a function which will return the args, 
     // that way, you don't have to determine the args for the function until the moment it's called 
     call.args = call.jitArgs? call.args():call.args; 
     $.each(call.args, function(index, arg){ 
      if(typeof arg === "function"){ 
      numFns++; 
      // Here's where we wrap the original function's callback 
      call.args[index] = function(){ 
       // when we get to this point, we know that the original function has totally completed, 
       // and we can call any functions chained to this one, or finish the whole process 
       arg.apply(null, arguments); // call the original callback 
       if(call.calls){ 
       // maybe we don't want to create the child calls until after 
       // the parent has returned. In that case, pass a function instead of an array 
       if(typeof call.calls === "function"){ 
        call.calls = call.calls(); 
       } 
       // if this call has any call of its own, send those out now 
       doCalls(call.calls); 
       } 
       decrimentCallbacksLeft(); 
      } 
      } 
     }); 
     if(numFns!=1){ 
      throw new Error("Each function passed to bundledAsync must have one and only one arg which is a function"); 
     } 
     // if(call.fn.length != call.args.length){ 
     // log.warn("The current function is being called with a different number of arguments that that with which it was declared. Should be: "+call.fn.length+", was: "+call.args.length+" \n" + call.fn.toString()); 
     // } 
     call.fn.apply(null, call.args); 
     }); 
    } 

    doCalls(calls); 
    } 
1

Recomiendo leer sobre continuation-passing style. Parece que su objetivo es, dado un conjunto de funciones que toman un argumento de continuación, encadenarlas de manera tal que la continuación pase a la siguiente función en el conjunto.

Aquí es una implementación de tal función:

function flow(funcArr, funcDone) { 
    function proceed(i) { 
     if (i < funcArr.length) { 
      return function() { 
       funcArr[i](proceed(i+1)); 
      } 
     } else { 
      return funcDone; 
     } 
    } 

    proceed(0)(); 
} 

Editar: Anon.'s answer es más corto y más simple.

Así es como funciona: proceed(i) devuelve una devolución de llamada que llama a la i ª función (o, funcDone, si no quede ninguno en la matriz). Como proceed(i) devuelve una devolución de llamada en lugar de hacerlo, podemos usar proceed(i+1) como función de continuidad.

Ejemplo de uso:

flow([ 

    function(cb) { print("Thing one"); cb(); }, 
    function(cb) { print("Thing two"); cb(); }, 
    function(cb) { print("Thing three"); cb(); }, 
    function(cb) { print("Thing four"); cb(); }, 

], function() { 

    print("Done."); 

}); 

Ahora intenta eliminar uno de los cb(); llamadas. Rompe la cadena, que es probablemente exactamente lo que quieres. Otra cosa interesante es que puede tomar cb, introducirlo en una variable global y llamarlo más tarde para reanudar el flujo.

Tenga en cuenta que hay un inconveniente en este enfoque: muchos (si no todos) los intérpretes de JavaScript no optimizan la recursividad final. Si el funcArr es demasiado largo, puede obtener un desbordamiento de la pila. Este es un cruce llevado a cabo por el uso del estilo de continuación de paso en JavaScript.

Cuestiones relacionadas