Pergunta

Este é meu primeiro post sobre stackoverflow, então, por favor, não me critique muito se eu parecer um idiota total ou se não conseguir ser perfeitamente claro.:-)

Aqui está o meu problema:Estou tentando escrever uma função javascript que "vincule" duas funções a outra, verificando a conclusão da primeira e executando a segunda.

A solução fácil para isso, obviamente, seria escrever uma metafunção que chamasse ambas as funções dentro de seu escopo.No entanto, se a primeira função for assíncrona (especificamente uma chamada AJAX) e a segunda função exigir os dados de resultado da primeira, isso simplesmente não funcionará.

Minha ideia para uma solução era dar à primeira função uma "sinalização", ou seja,fazendo com que ele crie uma propriedade pública "this.trigger" (inicializada como "0", definida como "1" após a conclusão) assim que for chamada;fazer isso deve permitir que outra função verifique o valor do sinalizador ([0,1]).Se a condição for atendida ("trigger == 1") a segunda função deverá ser chamada.

A seguir está um exemplo de código abstrato que usei para teste:

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

A parte HTML para teste:

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

Tenho certeza de que isso é algum problema com o escopo do javascript, pois verifiquei se o gatilho foi definido como "1" corretamente e está.Muito provavelmente a função "checkCall()" não recebe o objeto atualizado, mas apenas verifica sua instância antiga, que obviamente nunca sinaliza a conclusão definindo "this.trigger" como "1".Se sim, não sei como resolver esse problema.

De qualquer forma, espero que alguém tenha uma ideia ou experiência com esse tipo específico de problema.

Obrigado por ler!

Futebol

Foi útil?

Solução 4

Eu resolvi isso e parece funcionar perfeitamente bem agora.Vou postar meu código mais tarde, depois de resolvê-lo.Enquanto isso, muito obrigado pela sua ajuda!

Atualizar

Tentei o código no Webkit (Safari, Chrome), Mozilla e Opera.Parece funcionar bem.Aguardo qualquer resposta.

Atualização 2

Alterei o método tieExc() para integrar a sintaxe de chamada de função encadeada do Anurag.Agora você pode chamar quantas funções quiser após a verificação de conclusão, passando-as como argumentos.

Se você não estiver disposto a ler o código, tente: http://jsfiddle.net/UMuj3/ (aliás, JSFiddle é um site muito legal!).

Código JS:

/**/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>

Outras dicas

Você pode aproveitar um recurso do JS chamado encerramento.Combine isso com um padrão JS muito comum chamado "estilo de passagem de continuação" e você terá sua solução.(Nenhuma dessas coisas é original do JS, mas é muito usada no 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
}

O "estilo de passagem de continuação" refere-se ao retorno de chamada.Em vez de retornar um valor, cada função chama um retorno de chamada (a continuação) e fornece os resultados.

Você pode então unir os dois facilmente:

foo(input1, function(results1) {

    bar(results1, function(results2) {

        alert(results2);
    });
});

As funções anônimas aninhadas podem "ver" variáveis ​​do escopo em que residem.Portanto, não há necessidade de usar propriedades especiais para transmitir informações.

Atualizar

Para esclarecer, no trecho de código da sua pergunta, fica claro que você está pensando mais ou menos assim:

Eu tenho uma operação assíncrona de longa duração, então preciso saber quando termina para iniciar a próxima operação.Então, eu preciso tornar esse estado visível como uma propriedade.Em outros lugares, posso executar um loop, examinando repetidamente essa propriedade para ver quando ela muda para o estado "concluído", então eu sei quando continuar.

(E então, como fator complicador, o loop tem que usar setInterval para começar a correr e clearInterval para sair, para permitir que outro código JS seja executado - mas é basicamente um "loop de pesquisa").

Você não precisa fazer isso!

Em vez de fazer com que sua primeira função defina uma propriedade após a conclusão, faça com que ela chame uma função.

Para deixar isso absolutamente claro, vamos refatorar seu 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) ;
}

[Atualização 2:A propósito, há um bug aí.Você copia o valor atual do trigger propriedade em uma nova variável local chamada trigger.Então, no final, você atribui 1 a essa variável local.Ninguém mais será capaz de ver isso.Variáveis ​​locais são privadas para uma função. Mas você não precisa fazer nada disso de qualquer maneira, então continue lendo…]

Tudo o que precisamos fazer é dizer a essa função o que chamar quando terminar e nos livrar da configuração da propriedade:

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

Agora não há necessidade de sua "verificação de chamada" ou de seu especial tieExc ajudante.Você pode facilmente vincular duas funções com muito pouco código.

var mySpan = "#myspan";

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

Outra vantagem disso é que podemos passar parâmetros diferentes para a segunda função.Com a sua abordagem, os mesmos parâmetros são passados ​​para ambos.

Por exemplo, a primeira função pode fazer algumas chamadas para um serviço AJAX (usando jQuery por questões de brevidade):

function getCustomerBillAmount(name, callback) {

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

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

    });
}

Aqui, callback aceita o valor da fatura do cliente e o AJAX get chamada passa o valor recebido para a função que passamos, então o callback já é compatível e, portanto, pode atuar diretamente como retorno de chamada para a segunda chamada AJAX.Portanto, este é um exemplo de como unir duas chamadas assíncronas em sequência e envolvê-las no que parece (visto de fora) ser uma única função assíncrona.

Então podemos encadear isso com outra operação:

function displayBillAmount(amount) {

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

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

Ou poderíamos (novamente) ter usado uma função anônima:

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

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

Portanto, ao encadear chamadas de função como essa, cada etapa pode passar informações para a próxima etapa assim que estiverem disponíveis.

Ao fazer com que as funções executem um retorno de chamada quando terminarem, você fica livre de quaisquer limitações de como cada função funciona internamente.Ele pode fazer chamadas AJAX, temporizadores, qualquer coisa.Contanto que o retorno de chamada de "continuação" seja repassado, pode haver qualquer número de camadas de trabalho assíncrono.

Basicamente, em um sistema assíncrono, se você estiver escrevendo um loop para verificar uma variável e descobrir se ela mudou de estado, então algo deu errado em algum lugar.Em vez disso, deveria haver uma maneira de fornecer uma função que será chamada quando o estado mudar.

Atualização 3

Vejo em outros comentários que você mencionou que o problema real é o cache dos resultados, então todo o meu trabalho explicando isso foi uma perda de tempo.Esse é o tipo de coisa que você deve colocar na pergunta.

Atualização 4

Mais recentemente escrevi uma breve postagem no blog sobre o assunto de armazenamento em cache de resultados de chamadas assíncronas em JavaScript.

(fim da atualização 4)

Outra maneira de compartilhar resultados é fornecer uma maneira de um retorno de chamada "transmitir" ou "publicar" para vários assinantes:

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

Então:

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

Então deixe alguma operação lenta publicar seus resultados:

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

Quando essa chamada terminar, ela ligará para todos os três assinantes.

A resposta definitiva para este problema "me chame quando você estiver pronto" é um ligue de volta. Um retorno de chamada é basicamente uma função que você atribui a uma propriedade de objeto (como "Onload"). Quando o estado do objeto muda, a função é chamada. Por exemplo, essa função faz uma solicitação de Ajax para o URL e grita quando estiver 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);  
}

Obviamente, isso não é flexível o suficiente, porque presumivelmente queremos ações diferentes para diferentes chamadas do Ajax. Felizmente, o JavaScript é uma linguagem funcional, para que possamos simplesmente aprovar a ação necessária como um 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 função pode ser usada assim:

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

Conforme comentários, aqui um exemplo de como usar o Ajax dentro de um 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://....");

A idéia é "fechar" (alias) "isso" no manipulador de eventos, para que possa ser aprovado mais.

Bem-vindo ao SO!A propósito, você parece um idiota total e sua pergunta não é totalmente clara :)

Isso se baseia na resposta de @Daniel sobre o uso de continuações.É uma função simples que encadeia vários métodos.Muito parecido com o modo como o tubo | funciona em unix.Ele leva um conjunto de funções como argumentos que devem ser executados sequencialmente.O valor de retorno de cada chamada de função é passado para a próxima função como 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 usá-lo, crie um objeto de Chained passando todas as funções como parâmetros.Um exemplo que você pode teste no violino seria:

​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 usá-lo com uma solicitação AJAX, passe a função encadeada como retorno de chamada de sucesso para 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);  

O importante é que cada função depende da saída da função anterior, portanto você deve retornar algum valor em cada etapa.


Esta é apenas uma sugestão - atribuir a responsabilidade de verificar se os dados estão disponíveis localmente ou se uma solicitação HTTP deve ser feita aumentará a complexidade do sistema.Em vez disso, você poderia ter um gerenciador de solicitações opaco, muito parecido com o metaFunction você tem, e deixe-o decidir se os dados serão servidos localmente ou remotamente.

Aqui está um amostra Request objeto que lida com esta situação sem que nenhum outro objeto ou função saiba de onde os dados foram servidos:

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 usá-lo, você faria uma ligação para Request.get(..), e retorna dados armazenados em cache, se disponíveis, ou faz uma chamada AJAX caso contrário.Um terceiro parâmetro pode ser passado para controlar por quanto tempo os dados devem ser armazenados em cache, se você estiver procurando por controle granular sobre o 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

Uma solução muito simples seria tornar sua primeira chamada Ajax síncrona. É um dos parâmetros opcionais.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top