Pregunta

Este es mi primer post en stackoverflow, así que por favor no me llamas demasiado duro si encuentro como un idiota, total o si no puedo hacer ot mismo perfectamente claro. : -)

Aquí está mi problema:. Estoy tratando de escribir una función javascript que "lazos" dos funciones a otra mediante la comprobación de la finalización de la primera y luego ejecutar el segundo

La solución fácil para esto, obviamente, sería escribir una función de meta que llama a ambas funciones dentro de sus alcances. Sin embargo, si la primera función es asíncrona (en concreto una llamada AJAX) y la segunda función requiere datos de los resultados de la primera, que simplemente no funcionará.

Mi idea para una solución era dar a la primera función de una "bandera", es decir, por lo que es crear una propiedad pública "this.trigger" (inicializado como "0", el valor "1" al finalizar) una vez que se llama ; haciendo que debería hacer posible que otra función que controla la bandera para su valor ([0,1]). Si se cumple la condición ( "gatillo == 1") de la segunda función debe llamará.

El siguiente es un ejemplo de código abstracta que he utilizado para la prueba:

<script type="text/javascript" >

/**/function cllFnc(tgt) { //!! first function

    this.trigger = 0 ;
    var trigger = this.trigger ;

    var _tgt = document.getElementById(tgt) ; //!! changes the color of the target div to signalize the function's execution
        _tgt.style.background = '#66f' ;

    alert('Calling! ...') ;

    setTimeout(function() { //!! in place of an AJAX call, duration 5000ms

            trigger = 1 ;

    },5000) ;

}

/**/function rcvFnc(tgt) { //!! second function that should get called upon the first function's completion

    var _tgt = document.getElementById(tgt) ; //!! changes color of the target div to signalize the function's execution
        _tgt.style.background = '#f63' ;

    alert('... Someone picked up!') ;

}

/**/function callCheck(obj) {   

            //alert(obj.trigger ) ;      //!! correctly returns initial "0"                         

    if(obj.trigger == 1) {              //!! here's the problem: trigger never receives change from function on success and thus function two never fires 

                        alert('trigger is one') ;
                        return true ;
                    } else if(obj.trigger == 0) {
                        return false ;
                    }


}

/**/function tieExc(fncA,fncB,prms) {

        if(fncA == 'cllFnc') {
            var objA = new cllFnc(prms) ;   
            alert(typeof objA + '\n' + objA.trigger) ;  //!! returns expected values "object" and "0"
        } 

        //room for more case definitions

    var myItv = window.setInterval(function() {

        document.getElementById(prms).innerHTML = new Date() ; //!! displays date in target div to signalize the interval increments


        var myCallCheck = new callCheck(objA) ; 

            if( myCallCheck == true ) { 

                    if(fncB == 'rcvFnc') {
                        var objB = new rcvFnc(prms) ;
                    }

                    //room for more case definitions

                    window.clearInterval(myItv) ;

            } else if( myCallCheck == false ) {
                return ;
            }

    },500) ;

}

</script>

La parte HTML para la prueba:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/strict.dtd >

<html>

<head>

    <script type="text/javascript" >
       <!-- see above -->
    </script>

    <title>

      Test page

    </title>


</head>

<body>

    <!-- !! testing area -->

        <div id='target' style='float:left ; height:6em ; width:8em ; padding:0.1em 0 0 0; font-size:5em ; text-align:center ; font-weight:bold ; color:#eee ; background:#fff;border:0.1em solid #555 ; -webkit-border-radius:0.5em ;' >
            Test Div
        </div>

        <div style="float:left;" >
            <input type="button" value="tie calls" onmousedown="tieExc('cllFnc','rcvFnc','target') ;" />
        </div>

<body>


</html>

Estoy bastante seguro de que esto es una cuestión de alcance javascript como he comprobado si el gatillo se establece correctamente en "1" y lo hace. Es muy probable que la función "checkCall ()" no recibe el objeto actualizado pero en cambio sólo comprueba su antigua instancia que obviamente nunca indicadores de finalización de ajuste "this.trigger" a "1". Si es así no sé cómo abordar esta cuestión.

De todos modos, espero que alguien tiene una idea o experiencia con este tipo de problema en particular.

Gracias por leer!

FK

¿Fue útil?

Solución 4

Me ha trabajado a cabo y parece que funciona perfectamente bien ahora. Voy a publicar mi código más tarde después de haber lo arreglaron. Mientras tanto, muchas gracias por la ayuda que!

Actualizar

Probamos el código en Webkit (Safari, Chrome), Mozilla y Opera. Parece que funciona muy bien. Mirando hacia adelante a todas las respuestas.

Actualización 2

Me cambió el método tieExc () para integrar la sintaxis de llamada de función encadenada de Anurag. Ahora se puede llamar tantas funciones como desee en el check finalización haciéndolos pasar como argumentos.

Si no están dispuestos a leer el código, probarlo: http://jsfiddle.net/UMuj3/ (por cierto, jsFiddle es un sitio muy limpio!).

JS-Code:

/**/function meta() {

var myMeta = this ;

/**  **/this.cllFnc = function(tgt,lgt) { //!! first function

    this.trigger = 0 ;  //!! status flag, initially zero
    var that = this ;   //!! required to access parent scope from inside nested function

    var _tgt = document.getElementById(tgt) ; //!! changes the color of the target div to signalize the function's execution
    _tgt.style.background = '#66f' ;

    alert('Calling! ...') ;

    setTimeout(function() { //!! simulates longer AJAX call, duration 5000ms

        that.trigger = 1 ;  //!! status flag, one upon completion

    },5000) ;

} ;

/**  **/this.rcvFnc = function(tgt) { //!! second function that should get called upon the first function's completion

    var _tgt = document.getElementById(tgt) ; //!! changes color of the target div to signalize the function's execution
    _tgt.style.background = '#f63' ;

    alert('... Someone picked up!') ;

} ;

/**  **/this.callCheck = function(obj) {    

    return (obj.trigger == 1)   ?   true
        :   false
        ;

} ;

/**  **/this.tieExc = function() {

    var functions = arguments ;

    var myItv = window.setInterval(function() {

        document.getElementById('target').innerHTML = new Date() ; //!! displays date in target div to signalize the interval increments

        var myCallCheck = myMeta.callCheck(functions[0]) ; //!! checks property "trigger"

        if(myCallCheck == true) { 

            clearInterval(myItv) ;

            for(var n=1; n < functions.length; n++) {

                functions[n].call() ;

            }

        } else if(myCallCheck == false) { 
            return ;
        }

    },100) ;



} ;

}​

HTML

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/strict.dtd >

<html>

<head>

    <script type='text/javascript'  >
        <!-- see above -->
    </script>
    <title>

      Javascript Phased Execution Test Page

    </title>

</head>

<body>

        <div id='target' style='float:left ; height:7.5em ; width:10em ; padding:0.5em 0 0 0; font-size:4em ; text-align:center ; font-weight:bold ; color:#eee ; background:#fff;border:0.1em solid #555 ; -webkit-border-radius:0.5em ;' >
            Test Div
        </div>

        <div style="float:left;" >
            <input type="button" value="tieCalls()" onmousedown="var myMeta = new meta() ; var myCll = new myMeta.cllFnc('target') ; new myMeta.tieExc(myCll, function() { myMeta.rcvFnc('target') ; }, function() { alert('this is fun stuff!') ; } ) ;" /><br />
        </div>

<body>


</html>

Otros consejos

Se puede tomar ventaja de una característica de JS llamado cierre. Combine eso con un patrón muy común JS llamado "estilo de continuidad que pasa" y que tiene su solución. (Ninguna de estas cosas son originales a JS, pero son muy usadas en JS).

// a function
function foo(some_input_for_foo, callback)
{
    // do some stuff to get results

    callback(results); // call our callback when finished
}

// same again
function bar(some_input_for_bar, callback)
{
    // do some stuff to get results

    callback(results); // call our callback when finished
}

El "estilo continuación pasando" se refiere a la devolución de llamada. En lugar de devolver un valor, cada función llama a una devolución de llamada (la continuación) y le da los resultados.

A continuación, puede atar los dos juntos fácilmente:

foo(input1, function(results1) {

    bar(results1, function(results2) {

        alert(results2);
    });
});

Las funciones anónimas anidados puede "ver" las variables del ámbito en que viven. Así que no hay necesidad de utilizar las propiedades especiales para pasar información alrededor.

Actualizar

Para aclarar, en el fragmento de código de su pregunta, está claro que usted está pensando más o menos así:

  

Tengo un asíncrono de larga duración   operación, por lo que necesito saber cuando se   termina con el fin de iniciar la siguiente   operación. Así que necesito para hacer que   estado visible como una propiedad. Luego   en otra parte que se puede ejecutar en un bucle,   examinar en repetidas ocasiones que la propiedad de   ver cuando se cambia a la "completado"   estado, así que sé cuando para continuar.

(Y entonces, como un factor de complicación, el bucle tiene que utilizar setInterval para empezar a correr y clearInterval dejar de fumar, para permitir que otro código JS para correr - pero es básicamente un "bucle de sondeo", sin embargo).

Usted no necesita hacer eso!

En lugar de hacer su primera función establece una propiedad en la terminación, lo convierten en llamar a una función.

Para hacer esto absolutamente claro, vamos a refactorizar el código original:

function cllFnc(tgt) { //!! first function

    this.trigger = 0 ;
    var trigger = this.trigger ;

    var _tgt = document.getElementById(tgt) ; //!! changes the color...
    _tgt.style.background = '#66f' ;

    alert('Calling! ...') ;

    setTimeout(function() { //!! in place of an AJAX call, duration 5000ms

        trigger = 1 ;

    },5000) ;
}

[ Actualización 2 : Por cierto, hay un error allí. Copiar el valor actual de la propiedad trigger en una nueva variable local llamada trigger. Luego, al final se asigna 1 a la variable local. Nadie más va a ser capaz de ver eso. Las variables locales son privadas a una función. Pero usted no necesita hacer nada de esto de todos modos, así que sigue leyendo ... ]

Todo lo que tenemos que hacer es decir que la función de lo que llamar cuando se hace, y deshacerse de la configuración de la propiedad:

function cllFnc(tgt, finishedFunction) { //!! first function

    var _tgt = document.getElementById(tgt) ; //!! changes the color...
    _tgt.style.background = '#66f' ;

    alert('Calling! ...') ;

    setTimeout(function() { //!! in place of an AJAX call, duration 5000ms

        finishedFunction(); // <-------- call function instead of set property

    },5000) ;
}

Ahora hay ninguna necesidad de su "llamada de verificación" o su ayudante especial tieExc. Usted puede atar fácilmente dos funciones junto con muy poco código.

var mySpan = "#myspan";

cllFnc(mySpan, function() { rcvFnc(mySpan); });

Otra ventaja de esto es que podemos pasar diferentes parámetros a la segunda función. Con su enfoque, los mismos parámetros se pasan a la vez.

Por ejemplo, la primera función podría hacer un par de llamadas a un servicio de AJAX (jQuery para mayor brevedad):

function getCustomerBillAmount(name, callback) {

    $.get("/ajax/getCustomerIdByName/" + name, function(id) {

        $.get("/ajax/getCustomerBillAmountById/" + id), callback);

    });
}

A continuación, callback acepta la cantidad de la cuenta del cliente, y la llamada AJAX get pasa el valor recibido a la función que se pasa, por lo que el callback ya es compatible y por lo tanto puede actuar directamente como la devolución de llamada para la segunda llamada AJAX. Así que esto es en sí mismo un ejemplo de atar dos llamadas asíncronas juntos en secuencia y envolviéndolos en lo que parece (desde el exterior) para ser una sola función asíncrona.

Entonces podemos cadena, este con otra operación:

function displayBillAmount(amount) {

    $("#billAmount").text(amount); 
}

getCustomerBillAmount("Simpson, Homer J.", displayBillAmount);

O podríamos (otra vez) hemos utilizado una función anónima:

getCustomerBillAmount("Simpson, Homer J.", function(amount) {

    $("#billAmount").text(amount); 
});

Así que por el encadenamiento de llamadas a funciones como este, cada paso puede pasar información hacia adelante a la siguiente etapa tan pronto como esté disponible.

Al hacer funciones ejecutan una devolución de llamada y cuando estén listas, quedas libre de cualquier limitación a la forma en que funciona cada funciona internamente. Se puede hacer llamadas AJAX, temporizadores, lo que sea. Mientras la devolución de llamada "continuación" se pasa hacia adelante, no puede haber cualquier número de capas de trabajo asíncrono.

Básicamente, en un sistema asíncrono, si alguna vez se encuentra escribiendo un bucle para comprobar una variable y averiguar si ha cambiado de estado, entonces algo tiene algún lugar equivocado ido. En su lugar debe haber una manera de proporcionar una función de tsombrero será llamado cuando cambia el estado.

Actualización 3

Veo en otra parte de los comentarios que usted menciona que el problema real es el almacenamiento en caché los resultados, así que todo mi trabajo explicando esto era una pérdida de tiempo. Este es el tipo de cosas que debe poner en la pregunta.

Actualización 4

Más recientemente he escrito un post corto sobre el tema del almacenamiento en caché de resultados de llamadas asíncronas en JavaScript .

(final de la actualización 4)

Otra forma de compartir los resultados es proporcionar una manera para que una devolución de llamada de "emisión" o "publicar" a varios abonados:

function pubsub() {
    var subscribers = [];

    return {
        subscribe: function(s) {
            subscribers.push(s);
        },
        publish: function(arg1, arg2, arg3, arg4) {
            for (var n = 0; n < subscribers.length; n++) {
                subscribers[n](arg1, arg2, arg3, arg4);
            }
        }
    };
}

Así que:

finished = pubsub();

// subscribe as many times as you want:

finished.subscribe(function(msg) {
    alert(msg);
});

finished.subscribe(function(msg) {
    window.title = msg;
});

finished.subscribe(function(msg) {
    sendMail("admin@mysite.com", "finished", msg);
});

A continuación, dejar alguna operación lenta publicar sus resultados:

lookupTaxRecords("Homer J. Simpson", finished.publish);

Cuando una llamada que termina, ahora se llamarán los tres abonados.

La respuesta definitiva a esta "llámame cuando esté listo" es un problema de callback . Una devolución de llamada es básicamente una función que se asigna a una propiedad de objeto (como "onload"). Cuando el estado del objeto cambia, la función se llama. Por ejemplo, esta función hace una petición AJAX a la url dada y grita cuando es completa:

function ajax(url) {
    var req = new XMLHttpRequest();  
    req.open('GET', url, true);  
    req.onreadystatechange = function (aEvt) {  
        if(req.readyState == 4)
            alert("Ready!")
    }
    req.send(null);  
}

Por supuesto, esto no es lo suficientemente flexible, ya que, presumiblemente, queremos diferentes acciones para diferentes llamadas ajax. Afortunadamente, JavaScript es un lenguaje funcional, por lo que sólo tiene que pasar a la acción requerida como parámetro:

function ajax(url, action) {
    var req = new XMLHttpRequest();  
    req.open('GET', url, true);  
    req.onreadystatechange = function (aEvt) {  
        if(req.readyState == 4)
            action(req.responseText);
    }
    req.send(null);  
}

Esta segunda función se puede utilizar como este:

 ajax("http://...", function(text) {
      do something with ajax response  
 });

De acuerdo con los comentarios, aquí un ejemplo de cómo usar ajax dentro de un objeto

function someObj() 
{
    this.someVar = 1234;

    this.ajaxCall = function(url) {
        var req = new XMLHttpRequest();  
        req.open('GET', url, true);  

        var me = this; // <-- "close" this

        req.onreadystatechange = function () {  
            if(req.readyState == 4) {
                // save data...
                me.data = req.responseText;     
                // ...and/or process it right away
                me.process(req.responseText);   

            }
        }
        req.send(null);  
    }

    this.process = function(data) {
        alert(this.someVar); // we didn't lost the context
        alert(data);         // and we've got data!
    }
}


o = new someObj;
o.ajaxCall("http://....");

La idea es "cerrar" (alias) "este" en el controlador de eventos, por lo que se puede pasar más allá.

Bienvenido a SO! Por cierto, se encuentra con un idiota total y su pregunta es totalmente claro:)

Esta es la construcción de al @ respuesta de utilizar continuaciones de Daniel. Es una función simple que las cadenas de múltiples métodos juntos. Al igual que la forma en la tubería | trabaja en Unix. Se necesita un conjunto de funciones como sus argumentos que han de ser ejecutadas de forma secuencial. El valor de retorno de cada llamada de función se pasa a la siguiente función como un parámetro.

function Chain() {
    var functions = arguments;

    return function(seed) {
        var result = seed;

        for(var i = 0; i < functions.length; i++) {
            result = functions[i](result);
        }

        return result;
    }
}

Para usarlo, crear un objeto de Chained pasar todas las funciones como parámetros. Un ejemplo puede prueba en el violín sería:

​var chained = new Chain(
    function(a) { return a + " wo"; },
    function(a) { return a + "r"; },
    function(a) { return a + "ld!"; }
);

alert(chained('hello')); // hello world!

Para utilizarlo con una petición AJAX, pasan a la función de devolución de llamada encadenado como el éxito a la XMLHttpRequest.

​var callback = new Chain(
    function(response) { /* do something with ajax response */ },
    function(data) { /* do something with filtered ajax data */ }
);

var req = new XMLHttpRequest();  
req.open('GET', url, true);  
req.onreadystatechange = function (aEvt) {  
    if(req.readyState == 4)
        callback(req.responseText);
}
req.send(null);  

Lo importante es que cada función depende de la salida de la función anterior, por lo que debe devolver algún valor en cada etapa.


Esto es sólo una sugerencia - dando la responsabilidad de comprobar si los datos están disponibles a nivel local o una solicitud HTTP debe hacerse va a aumentar la complejidad del sistema. En su lugar, usted podría tener un gestor de peticiones opaco, al igual que el metaFunction que tiene, y dejar que decidir si los datos van a ser servido forma local o remota.

Aquí está una muestra Request objeto que se encarga de esta situación sin ningún otro objeto o funciones sabiendo que los datos fue servido en:

var Request = {
    cache: {},

    get: function(url, callback) {
        // serve from cache, if available
        if(this.cache[url]) {
            console.log('Cache');
            callback(this.cache[url]);
            return;
        }
        // make http request
        var request = new XMLHttpRequest();
        request.open('GET', url, true);
        var self = this;
        request.onreadystatechange = function(event) {
            if(request.readyState == 4) {
                self.cache[url] = request.responseText;
                console.log('HTTP');
                callback(request.responseText);
            }
        };
        request.send(null);
    }
};

Para usarlo, usted podría hacer una llamada a Request.get(..), y devuelve los datos en caché si está disponible o hace una llamada AJAX lo contrario. Un tercer parámetro se podría pasar al control de cuánto tiempo los datos deben almacenarse en caché para, si usted está buscando un control granular sobre el almacenamiento en caché.

Request.get('<url>', function(response) { .. }); // HTTP
// assuming the first call has returned by now
Request.get('<url>', function(response) { .. }); // Cache
Request.get('<url>', function(response) { .. }); // Cache

Una solución muy sencilla sería la de realizar la primera llamada ajax síncrona. Es uno de los parámetros opcionales.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top