Domanda

Questo è il mio primo post su StackOverflow, quindi per favore non mi fiamma troppo difficile se mi imbatto in come un imbecille totale o se sono in grado ot farmi perfettamente chiaro. : -)

Ecco il mio problema:. Sto cercando di scrivere una funzione javascript che "i legami" due funzioni per un altro controllando il completamento del primo uno e poi l'esecuzione della seconda

La soluzione facile a questo, ovviamente, sarebbe quello di scrivere un meta-funzione che chiama entrambe le funzioni all'interno del suo campo di applicazione. Tuttavia, se la prima funzione è asincrona (specificamente una chiamata AJAX) e la seconda funzione richiede il primo uno dati dei risultati, che semplicemente non funziona.

La mia idea per una soluzione era quella di dare la prima funzione di un "flag", vale a dire che lo rende creare una proprietà pubblica "this.trigger" (inizializzato come "0", impostato a "1" al termine) una volta che è chiamato ; facendo che dovrebbe rendere possibile per un'altra funzione per controllare la bandiera per il suo valore ([0,1]). Se la condizione è soddisfatta ( "innesco == 1") la seconda funzione dovrebbe avere chiamato.

Il seguente è un codice di esempio astratto che ho usato per il test:

<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 per il test:

<!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>

Sono abbastanza sicuro che questo è un problema con javascript portata come ho controllato se il trigger viene impostato su "1" in modo corretto e lo fa. Molto probabilmente la funzione "checkCall ()" non riceve l'oggetto aggiornato, ma controlla invece solo la sua vecchia istanza che ovviamente mai bandiere completamento impostando "this.trigger" a "1". Se è così non so come affrontare tale questione.

In ogni caso, spero che qualcuno ha un'idea o esperienza con questo particolare tipo di problema.

Grazie per la lettura!

FK

È stato utile?

Soluzione 4

Ho lavorato fuori e sembra funzionare perfettamente ora. Io posto il mio codice più tardi, dopo che ho ordinato fuori. Nel frattempo, grazie mille per l'assistenza!

Aggiorna

provato il codice a Webkit (Safari, Chrome), Mozilla e Opera. Sembra funzionare bene. In attesa di eventuali risposte.

Aggiorna 2

Ho cambiato il metodo tieExc () per integrare la sintassi funzione di chiamata incatenato di Anurag. Ora è possibile chiamare il maggior numero di funzioni che si desidera al momento del check completamento passandoli come argomenti.

Se non siete inclini a leggere il codice, provare: http://jsfiddle.net/UMuj3/ (a proposito, JSFiddle è un sito veramente pulito!).

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>

Altri suggerimenti

È possibile usufruire di una caratteristica di JS chiamata chiusura. Che si combinano con un modello molto comune JS chiamata "continuazione passando stile" e avete la vostra soluzione. (Nessuna di queste cose sono originali di JS, ma sono fortemente utilizzati in 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
}

Il "continuazione passando stile" si riferisce alla funzione di callback. Invece di restituire un valore, ogni funzione chiama un callback (continuazione) e dà i risultati.

È possibile quindi legare le due cose insieme facilmente:

foo(input1, function(results1) {

    bar(results1, function(results2) {

        alert(results2);
    });
});

Le funzioni anonime nidificate possono "vedere" le variabili dal campo di applicazione in cui vivono. Quindi non c'è alcuna necessità di utilizzare particolari proprietà per trasmettere informazioni.

Aggiorna

Per chiarire, nel frammento di codice della tua domanda, è chiaro che si sta pensando più o meno in questo modo:

  

Ho un asincrono di lunga durata   operazione, quindi ho bisogno di sapere quando è   finiture al fine di avviare il prossimo   operazione. Così ho bisogno di fare che   stato visibile come una proprietà. Poi   altrove posso eseguire in un ciclo,   più volte l'esame che la proprietà di   vedere quando passa al "completamento"   stato, quindi so quando per continuare.

(E poi come un fattore di complicazione, il ciclo deve usare setInterval per iniziare a correre e clearInterval di smettere, per permettere altro codice JS per eseguire - ma è fondamentalmente un "ciclo di polling", tuttavia).

Non è necessario farlo!

Invece di fare la vostra prima funzione impostata una proprietà al termine, ne fanno chiamare una funzione.

Per fare questo assolutamente chiaro, cerchiamo di refactoring del codice originale:

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

[ Aggiorna 2 : A proposito, c'è un bug lì. Si copia il valore corrente della proprietà trigger in una nuova variabile locale chiamata trigger. Poi alla fine si assegna 1 a quella variabile locale. Nessun altro sta per essere in grado di vedere che. Le variabili locali sono private a una funzione. Ma non c'è bisogno di fare nulla di tutto questo in ogni caso, in modo da continuare a leggere ... ]

Tutto ciò che dobbiamo fare è dire che la funzione di come chiamarlo quando è fatto, e di sbarazzarsi della proprietà-impostazione:

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

C'è ora bisogno per il vostro "call-check" o il vostro aiutante speciale tieExc. Si può facilmente legare due funzioni con pochissimo codice.

var mySpan = "#myspan";

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

Un altro vantaggio di questo è che possiamo trasmettere diversi parametri alla seconda funzione. Con il vostro approccio, gli stessi parametri vengono passati ad entrambi.

Ad esempio, la prima funzione potrebbe fare un paio di chiamate a un servizio di AJAX (utilizzando jQuery per brevità):

function getCustomerBillAmount(name, callback) {

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

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

    });
}

Qui, callback accetta l'importo della fattura cliente, e la chiamata get AJAX passa il valore ricevuto alla funzione passiamo, quindi il callback è già compatibile e quindi può direttamente agire come callback per la seconda chiamata AJAX. Quindi questo è esso stesso un esempio di legare due chiamate asincrone insieme in sequenza e avvolgendoli in quello che sembra (dall'esterno) ad una singola funzione asincrona.

Quindi possiamo catena di questo con un'altra operazione:

function displayBillAmount(amount) {

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

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

O potremmo (di nuovo) abbiamo usato una funzione anonima:

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

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

Quindi, concatenando le chiamate di funzione come questo, ogni passo può passare informazioni avanti alla fase successiva non appena è disponibile.

Rendendo funzioni eseguono un callback quando hanno finito, si sono liberati dal eventuali limitazioni al funzionamento di ogni funziona internamente. Si può fare le chiamate AJAX, timer, qualsiasi cosa. Finché il callback "continuazione" si passa in avanti, ci può essere un qualsiasi numero di strati di lavoro asincrono.

In sostanza, in un sistema asincrono, se mai trovate a scrivere un ciclo per controllare una variabile e scoprire se ha cambiato stato, allora qualcosa è andato storto da qualche parte. Invece ci dovrebbe essere un modo per fornire una funzione tcappello verrà chiamato quando i cambiamenti di stato.

Aggiorna 3

vedo altrove nei commenti si menziona che il problema reale è caching risultati, quindi tutto il mio lavoro per spiegare questa è stata una perdita di tempo. Questo è il genere di cosa che si dovrebbe mettere in questione.

Aggiorna 4

Più di recente ho scritto un breve post sul blog in tema di caching risultati chiamata asincrona in JavaScript .

(fine aggiornamento 4)

Un altro modo per condividere i risultati è quello di fornire un modo per un callback per "broadcast" o "Pubblica" per più utenti:

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

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

Poi lascia un po 'di un'operazione lenta pubblicare i suoi risultati:

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

Quando che una chiamata termina, sarà ora chiamare tutti e tre gli abbonati.

La risposta definitiva a questa "Chiamami quando sei pronto" problema è un callback . Una callback è fondamentalmente una funzione che si assegna alla proprietà di un oggetto (come "onload"). Quando lo stato oggetto cambia, la funzione viene richiamata. Ad esempio, questa funzione rende una richiesta Ajax per l'URL dato e urla quando è completo:

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

Naturalmente, questo non è abbastanza flessibile, perché vogliamo che presumibilmente azioni diverse per le chiamate Ajax differenti. Fortunatamente, JavaScript è un linguaggio funzionale, in modo da poter semplicemente passare l'azione richiesta come parametro:

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

Questa seconda funzione può essere utilizzata in questo modo:

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

Come per i commenti, ecco un esempio di come utilizzare Ajax all'interno di un oggetto

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://....");

L'idea è quella di "chiudere" (alias) "questo" nel gestore eventi, in modo che possa essere passato oltre.

Benvenuti a SO! Btw, si può incontrare come un imbecille totale e la tua domanda è del tutto chiaro:)

Questa è la costruzione su @ risposta di Daniel di utilizzare continuazioni. Si tratta di una semplice funzione che catene più metodi insieme. Molto simile come il tubo di | lavora in UNIX. Ci vuole un insieme di funzioni come argomenti che devono essere eseguite sequenzialmente. Il valore di ritorno di ogni chiamata di funzione viene passata al prossimo funzione come parametro.

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

Per usarlo, creare un oggetto da Chained passare tutte le funzioni come parametri. Un esempio è possibile prova sul violino sarebbe:

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

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

Per usarlo con una richiesta AJAX, passare la funzione incatenato come il successo di richiamata al 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);  

La cosa importante è che ogni funzione dipende l'uscita della funzione precedente, quindi è necessario restituire un valore in ogni fase.


Questa è solo un suggerimento - dando la responsabilità di verificare se i dati sono disponibili a livello locale o una richiesta HTTP deve essere fatta è destinato ad aumentare la complessità del sistema. Invece, si potrebbe avere una richiesta di direttore opaca, molto simile al metaFunction che hai, e lasciare che a decidere se i dati devono essere servito locale o in remoto.

Ecco un oggetto campione Request che gestisce questa situazione, senza altri oggetti o funzioni sapere dove i dati è stato servito da:

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

Per usarlo, si dovrebbe effettuare una chiamata a Request.get(..), e restituisce i dati memorizzati nella cache se disponibile, oppure fa una chiamata AJAX altrimenti. Un terzo parametro potrebbe essere passato per controllare quanto tempo i dati devono essere memorizzati nella cache per, se siete alla ricerca di un controllo granulare su caching.

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 soluzione molto semplice sarebbe quella di effettuare la prima sincrona ajax chiamata. E 'uno dei parametri opzionali.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top