“Поэтапное” выполнение функций в JavaScript

Это - мое первое сообщение на stackoverflow, поэтому не горите меня слишком трудно, если я сталкиваюсь как общий кретин или если я - неспособный ot, делают меня совершенно ясным.:-)

Вот моя проблема: я пытаюсь записать функцию JavaScript, которая "связывает" две функции с другим путем проверки первого завершение и затем выполнения второго.

Легкое решение этого, очевидно, состояло бы в том, чтобы записать функцию meta, которая вызывает обе функции в, он - объем. Однако, если первая функция является асинхронной (конкретно вызов Ajax), и вторая функция требует первого данные результата, которые просто не будут работать.

Моя идея для решения состояла в том, чтобы дать первой функции "флаг", т.е. то, чтобы заставлять ее создать общественную собственность "this.trigger" (инициализированный как "0", установите на "1" после завершения), после того как это называют; выполнение, которое должно позволить другой функции проверить флаг на его значение ([0,1]). Если условие соблюдают ("триггер == 1"), вторая функция должна быть вызвана.

Следующее является абстрактным примером кода, который я использовал для тестирования:

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

Часть 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>

      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>

Я вполне уверен, что это - некоторая проблема с объемом JavaScript, поскольку я проверил, установлен ли триггер на "1" правильно, и это делает. Очень вероятно "checkCall ()" функция не получает обновленный объект, но вместо этого только проверяет его старый экземпляр, который, очевидно, никогда не отмечает завершение путем установки "this.trigger" к "1". Раз так я не знаю, как решить ту проблему.

Так или иначе надежда у кого-то есть идея или опыт с этим конкретным видом проблемы.

Спасибо за чтение!

FK

5
задан FK82 31 March 2010 в 12:22
поделиться

5 ответов

Я разобрался, и, похоже, теперь он работает отлично. Я отправлю свой код позже, когда разберусь с ним. А пока большое спасибо за вашу помощь!

Обновление

Пробовал код в Webkit (Safari, Chrome), Mozilla и Opera. Кажется, работает нормально. С нетерпением жду ответов.

Обновление 2

Я изменил метод tieExc (), чтобы интегрировать синтаксис вызова связанных функций Anurag. Теперь вы можете вызывать столько функций, сколько хотите, по завершении проверки, передавая их в качестве аргументов.

Если вы не склонны читать код, попробуйте: http://jsfiddle.net/UMuj3/ (кстати, JSFiddle - действительно отличный сайт!).

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>
1
ответ дан 18 December 2019 в 11:55
поделиться

Окончательный ответ на эту проблему «позвони мне, когда будешь готов» - это обратный вызов . Обратный вызов - это в основном функция, которую вы назначаете свойству объекта (например, «onload»). При изменении состояния объекта вызывается функция. Например, эта функция делает запрос ajax по заданному URL-адресу и кричит, когда он завершается:

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

Конечно, это недостаточно гибко, потому что мы, предположительно, хотим разные действия для разных вызовов ajax. К счастью, javascript - это функциональный язык, поэтому мы можем просто передать необходимое действие в качестве параметра:

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

Эту вторую функцию можно использовать следующим образом:

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

Согласно комментариям, вот пример использования ajax внутри объекта

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

Идея состоит в том, чтобы «закрыть» (присвоить псевдоним) «this» в обработчике событий, чтобы его можно было передать дальше.

5
ответ дан 18 December 2019 в 11:55
поделиться

Очень простым решением было бы сделать ваш первый вызов ajax синхронным. Это один из необязательных параметров.

0
ответ дан 18 December 2019 в 11:55
поделиться

Вы можете воспользоваться функцией JS, называемой закрытием. Объедините это с очень распространенным шаблоном JS, который называется «стиль передачи продолжения», и вы получите свое решение. (Ни одна из этих вещей не является оригинальной для JS, но активно используется в 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
}

«Стиль передачи продолжения» относится к обратному вызову. Вместо возврата значения каждая функция вызывает обратный вызов (продолжение) и выдает ему результаты.

Затем вы можете легко связать их вместе:

foo(input1, function(results1) {

    bar(results1, function(results2) {

        alert(results2);
    });
});

Вложенные анонимные функции могут «видеть» переменные из области видимости, в которой они находятся. Таким образом, нет необходимости использовать специальные свойства для передачи информации.

Обновление

Чтобы уточнить, во фрагменте кода вашего вопроса ясно, что вы думаете примерно так:

У меня есть длительная асинхронная операция , поэтому мне нужно знать, когда она завершается, чтобы начать следующую операцию . Поэтому мне нужно сделать это состояние видимым как свойство. Затем в другом месте я могу запустить цикл, неоднократно проверяя это свойство, чтобы увидеть, когда оно переходит в состояние "завершено" , поэтому я знаю, когда нужно Продолжить.

(Кроме того, в качестве усложняющего фактора, цикл должен использовать setInterval для запуска и clearInterval для выхода, чтобы разрешить выполнение другого JS-кода, но в основном это " цикл опроса "тем не менее).

Вам не нужно этого делать!

Вместо того, чтобы заставлять вашу первую функцию устанавливать свойство по завершении, заставьте ее вызывать функцию.

Чтобы сделать это абсолютно ясным, давайте реорганизуем исходный код:

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

[ Обновление 2 : Кстати, там ошибка.Вы копируете текущее значение свойства триггера в новую локальную переменную, называемую триггер . Затем в конце этой локальной переменной присваивается 1. Больше никто этого не увидит. Локальные переменные являются частными для функции. Но в любом случае вам не нужно ничего делать, так что продолжайте читать ... ]

Все, что нам нужно сделать, это сказать этой функции, что вызывать, когда она будет выполнена, и избавиться от установка свойства:

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

Теперь нет необходимости в вашей "проверке звонка" или в вашем специальном помощнике tieExc . Вы можете легко связать две функции вместе с очень небольшим кодом.

var mySpan = "#myspan";

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

Еще одно преимущество этого заключается в том, что мы можем передавать другие параметры второй функции. При вашем подходе к обоим передаются одни и те же параметры.

Например, первая функция может выполнить несколько вызовов службы AJAX (с использованием jQuery для краткости):

function getCustomerBillAmount(name, callback) {

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

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

    });
}

Здесь обратный вызов принимает сумму счета клиента, а AJAX Вызов get передает полученное значение функции, которую мы передаем, поэтому обратный вызов уже совместим и может напрямую действовать как обратный вызов для второго вызова AJAX. Так что это сам по себе пример последовательного связывания двух асинхронных вызовов вместе и их обертывания в том, что кажется (снаружи) одной асинхронной функцией.

Затем мы можем связать это с другой операцией:

function displayBillAmount(amount) {

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

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

Или мы могли бы (снова) использовать анонимную функцию:

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

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

Итак, объединяя вызовы функций, как это, каждый шаг может передавать информацию на следующий шаг, как только как есть.

Заставляя функции выполнять обратный вызов, когда они завершены, вы освобождаетесь от любых ограничений на то, как каждая функция работает внутри. Он может выполнять вызовы AJAX, таймеры и т. Д. Пока обратный вызов «продолжения» передается вперед, может быть любое количество уровней асинхронной работы.

По сути, в асинхронной системе, если вы когда-нибудь обнаружите, что пишете цикл для проверки переменной и выяснения, изменилось ли ее состояние, значит, где-то что-то пошло не так. Вместо этого должен быть способ предоставить функцию, которая будет вызываться при изменении состояния.

Обновление 3

Я вижу в другом месте в комментариях, где вы упоминаете, что настоящая проблема заключается в кэшировании результатов, поэтому вся моя работа по объяснению этого была пустой тратой времени. Это то, что вам следует задать в вопросе.

Обновление 4

Совсем недавно я написал короткую запись в блоге на тему кэширования результатов асинхронных вызовов в JavaScript .

(конец обновления 4)

Другой способ поделиться результатами - предоставить способ для одного обратного вызова для «широковещательной передачи» или «публикации» нескольким подписчикам:

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

Затем позвольте некоторой медленной операции опубликуйте его результаты:

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

Когда этот вызов завершится, он будет вызывать всех трех абонентов.

8
ответ дан 18 December 2019 в 11:55
поделиться

Добро пожаловать на SO! Btw, Вы выглядите полным профаном и Ваш вопрос совершенно непонятен :)

Это основано на ответе @Daniel об использовании продолжений. Это простая функция, которая связывает несколько методов вместе. Это похоже на то, как работает pipe | в unix. В качестве аргументов она принимает набор функций, которые должны выполняться последовательно. Возвращаемое значение каждой функции передается следующей функции в качестве параметра.

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

Чтобы использовать его, создайте объект из Chained, передав все функции в качестве параметров. Пример, который вы можете проверить на fiddle, будет следующим:

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

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

Чтобы использовать его с AJAX-запросом, передайте цепочку функций в качестве обратного вызова успеха в 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);  

Важно то, что каждая функция зависит от вывода предыдущей функции, поэтому на каждом этапе вы должны возвращать какое-то значение.


Это просто предложение - передача ответственности за проверку того, доступны ли данные локально или необходимо выполнить HTTP-запрос, приведет к увеличению сложности системы. Вместо этого вы могли бы иметь непрозрачный менеджер запросов, подобно метафункции, которую вы имеете, и позволить ему решать, должны ли данные обслуживаться локально или удаленно.

Вот пример объекта Request, который обрабатывает эту ситуацию без каких-либо других объектов или функций, знающих, откуда были получены данные:

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

Чтобы использовать его, вы должны сделать вызов Request.get(...), и он вернет кэшированные данные, если они доступны, или сделает AJAX вызов в противном случае. Третий параметр может быть передан для контроля длительности кэширования данных, если вам нужен детальный контроль над кэшированием.

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
1
ответ дан 18 December 2019 в 11:55
поделиться
Другие вопросы по тегам:

Похожие вопросы: