Question

Ceci est mon premier post sur stackoverflow, alors s'il vous plaît ne me flamme pas trop dur si je viens à travers comme un nigaud totale ou si je ne peux pas me faire ot parfaitement clair. : -)

Voici mon problème: je. Essaie d'écrire une fonction javascript que deux fonctions à une autre « liens » en vérifiant l'achèvement de la première, puis d'exécuter le second

La solution facile à ce serait évidemment d'écrire une fonction méta qui appelle les deux fonctions au sein de son champ d'application. Cependant, si la première fonction est asynchrone (plus précisément un appel AJAX) et la deuxième fonction nécessite des données de résultat de la première, qui ne fonctionnera pas.

Mon idée d'une solution a été de donner la première fonction d'un « drapeau », ce qui en fait de créer une propriété publique « this.trigger » (initialisé « 0 », réglé sur « 1 » à la fin) une fois qu'il est appelé ; faire cela devrait permettre à une autre fonction pour vérifier le drapeau de sa valeur ([0,1]). Si la condition est remplie ( « déclencheur == 1 ») la deuxième fonction doit s'appeler.

Voici un exemple de code abstrait que je l'ai utilisé pour le 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 partie HTML pour le 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>

Je suis sûr que ce soit une question avec javascript portée comme je l'ai vérifié si le déclencheur se correctement réglé sur « 1 » et il le fait. Très probablement la fonction « checkCall () » ne reçoit pas l'objet mis à jour mais ne vérifie son ancienne instance qui, évidemment, ne drapeaux de fin en réglant « this.trigger » à « 1 ». Si donc je ne sais pas comment répondre à cette question.

Quoi qu'il en soit, l'espoir que quelqu'un a une idée ou une expérience avec ce genre de problème particulier.

Merci d'avoir lu!

FK

Était-ce utile?

La solution 4

Je l'ai travaillé dessus et il semble fonctionner parfaitement bien maintenant. Je vais poster mon code plus tard après avoir trié dehors. En attendant, merci beaucoup pour vous aide!

Mise à jour

Essayé le code dans Webkit (Safari, Chrome), Mozilla et Opera. Il semble très bien fonctionner. Dans l'attente de réponses.

Mise à jour 2

J'ai changé la méthode tieExc () pour intégrer la syntaxe d'appel de fonction de chaîne Anurag. Maintenant, vous pouvez appeler autant de fonctions que vous le souhaitez lors de l'enregistrement d'achèvement en les faisant passer comme arguments.

Si vous n'êtes pas enclin à lire le code, essayez: http://jsfiddle.net/UMuj3/ (BTW, jsFiddle est un site vraiment bien!).

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>

Autres conseils

Vous pouvez profiter d'une caractéristique de JS appelé fermeture. Combinez cela avec un modèle JS très courant appelé « style passage de continuation » et vous avez votre solution. (Aucune de ces choses sont d'origine à JS, mais sont fortement utilisés dans 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
}

Le « style passage de continuation » fait référence à la fonction de rappel. Au lieu de retourner une valeur, chaque fonction appelle un rappel (la suite) et donne les résultats.

Vous pouvez ensuite attacher les deux ensemble facilement:

foo(input1, function(results1) {

    bar(results1, function(results2) {

        alert(results2);
    });
});

Les fonctions anonymes imbriquées peuvent « voir » les variables du champ dans lequel ils vivent. Il n'y a donc pas besoin d'utiliser des propriétés particulières pour transmettre des informations autour.

Mise à jour

Pour préciser, dans l'extrait de code de votre question, il est clair que vous pensez à peu près comme ceci:

  

J'ai un asynchrone de longue durée   opération, donc je dois savoir quand il   se termine pour commencer la prochaine   opération. Donc, je dois faire ce   état visible comme une propriété. ensuite   ailleurs je peux courir dans une boucle,   examiner à plusieurs reprises que la propriété à   voir quand il passe à la « terminée »   état, donc je sais quand continuer.

(Et puis comme facteur de complication, la boucle doit utiliser setInterval pour commencer à courir et clearInterval de quitter, pour permettre à tout autre code JS à courir - mais il est en fait une « boucle d'interrogation » quand même).

Vous n'avez pas besoin de le faire!

Au lieu de faire votre première fonction définir une propriété à la fin, faire appel à une fonction.

Pour que cela soit absolument clair, refactorisons votre code d'origine:

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

[ Mise à jour 2 : Soit dit en passant, il y a un bug là. Vous copiez la valeur actuelle de la propriété trigger dans une nouvelle variable locale appelée trigger. Puis à la fin vous attribuez 1 à cette variable locale. Personne d'autre ne va être en mesure de voir. Les variables locales sont privées à une fonction. Mais vous n'avez pas besoin de faire tout cela de toute façon, donc continuer à lire ... ]

Tout ce que nous avons à faire est de dire que la fonction que d'appeler quand il est fait, et se débarrasser de la propriété d'établissement:

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

Il y a maintenant pas besoin de votre « appel-check » ou votre aide spéciale tieExc. Vous pouvez facilement lier deux fonctions en même temps que très peu de code.

var mySpan = "#myspan";

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

Un autre avantage est que nous pouvons passer des paramètres différents à la deuxième fonction. Avec votre approche, sont passés les mêmes paramètres à la fois.

Par exemple, la première fonction peut faire quelques appels à un service AJAX (en utilisant jQuery par souci de concision):

function getCustomerBillAmount(name, callback) {

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

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

    });
}

Ici, callback accepte le montant de la facture du client, et l'appel get AJAX passe la valeur reçue à la fonction que nous transmettons, de sorte que le callback est déjà compatible et peut donc agir directement comme le rappel pour le second appel d'AJAX. Donc, c'est lui-même un exemple de lier deux appels asynchrones ensemble dans l'ordre et les envelopper dans ce qui semble (de l'extérieur) pour une seule fonction asynchrone.

Ensuite, nous pouvons chaîne cela avec une autre opération:

function displayBillAmount(amount) {

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

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

Ou nous pourrions (encore une fois) ont utilisé une fonction anonyme:

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

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

en enchaînant les appels de fonction comme celle-ci, chaque étape peut transmettre des informations vers l'avant à l'étape suivante dès qu'il est disponible.

En faisant exécuter les fonctions d'un rappel quand ils sont fait, vous êtes libérés de toute limite à la façon dont chacune des fonctions de fonctionnement interne. Il peut faire des appels AJAX, minuteries, peu importe. Tant que le rappel « de continuation » est passé en avant, il peut y avoir un certain nombre de couches de travail asynchrone.

En fait, dans un système asynchrone, si vous vous retrouvez à écrire une boucle pour vérifier une variable et de savoir si elle a changé d'état, alors quelque chose ne va pas quelque part. Au lieu de cela il devrait y avoir un moyen de fournir une fonction tchapeau sera appelé lorsque les changements d'état.

Mise à jour 3

Je vois ailleurs dans les commentaires que vous mentionnez que le problème réel est des résultats en cache, donc tout mon travail en expliquant ce fut une perte de temps. C'est le genre de chose que vous devriez mettre dans la question.

Mise à jour 4

Plus récemment, j'ai écrit un blog court sur le sujet de la mise en cache des résultats d'appels asynchrones en JavaScript .

(fin de la mise à jour 4)

Une autre façon de partager les résultats est de fournir un moyen pour un rappel à « émission » ou « publier » à plusieurs abonnés:

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

Ensuite, laissez une opération lente publier ses résultats:

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

Quand un appel que se termine, il va maintenant appeler les trois abonnés.

La réponse définitive à ce problème « appelez-moi quand vous êtes prêt » est un callback . Un rappel est essentiellement une fonction que vous attribuez à une propriété d'objet (comme « onload »). Lorsque l'état de l'objet change, la fonction est appelée. Par exemple, cette fonction fait une demande de paiement ajax à l'URL donnée et crie quand il est complet:

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

Bien sûr, cela ne suffit pas flexible, parce que nous voulons sans doute différentes actions pour les différents appels ajax. Heureusement, javascript est un langage fonctionnel, afin que nous puissions simplement passer l'action requise comme paramètre:

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

peut être utilisé Cette deuxième fonction comme ceci:

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

Selon les commentaires, voici un exemple comment utiliser ajax dans un objet

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'idée est de « fermer » (Aliased) « ce » dans le gestionnaire d'événements, de sorte qu'il peut être transmis plus loin.

Bienvenue sur SO! BTW, vous tombez sur un nigaud totale et votre question est tout à fait claire:)

Ceci est construit sur @ la réponse de Daniel d'utiliser continuations. Il est une fonction simple que les chaînes multiples méthodes ensemble. Tout comme la façon dont la conduite | fonctionne en unix. Il prend un ensemble de fonctions que ses arguments qui doivent être exécutées séquentiellement. La valeur de retour de chaque appel de fonction est transmise à la fonction suivante en tant que paramètre.

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

Pour l'utiliser, créez un objet de Chained passer toutes les fonctions en tant que paramètres. Un exemple vous pouvez test sur violon serait:

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

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

Pour l'utiliser avec une requête AJAX, passez la fonction enchaîné comme le rappel de succès à 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);  

La chose importante est que chaque fonction dépend de la sortie de la fonction précédente, vous devez donc retourner une valeur à chaque étape.


Ceci est juste une suggestion - donnant la responsabilité de vérifier si les données sont disponibles localement ou une requête HTTP doit être faite va augmenter la complexité du système. Au lieu de cela, vous pourriez avoir un gestionnaire de requêtes opaque, un peu comme le metaFunction que vous avez, et laissez-le décider si les données doivent être servis localement ou à distance.

Voici un objet Request qui gère cette situation sans d'autres objets ou fonctions savoir où les données a servi de:

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

Pour l'utiliser, vous faire un appel à Request.get(..), et renvoie les données mises en cache si elles sont disponibles ou fait un appel AJAX autrement. Un troisième paramètre pourrait être passé pour contrôler combien de temps les données doivent être mises en cache pour, si vous cherchez un contrôle granulaire sur la mise en cache.

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

Une solution très simple serait de faire votre première synchronisation d'appel ajax. Il est l'un des paramètres facultatifs.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top