Вопрос

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

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

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

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

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

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

ФК

Это было полезно?

Решение 4

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

Обновить

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

Обновление 2

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

Другие советы

Вы можете воспользоваться преимуществами функции JS, называемой closure.Объедините это с очень распространенным шаблоном 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:Кстати, там есть ошибка.Вы копируете текущее значение trigger свойство в новую локальную переменную, называемую trigger.Затем в конце вы присваиваете 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);

    });
}

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

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

Окончательный ответ на эту проблему "позвони мне, когда будешь готов" - это обратный вызов.Обратный вызов - это в основном функция, которую вы присваиваете свойству объекта (например, "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://....");

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

Добро пожаловать в SO!Кстати, вы производите впечатление полного кретина, и ваш вопрос совершенно непонятен :)

Это основано на ответе @Daniel об использовании продолжений.Это простая функция, которая объединяет несколько методов вместе.Очень похоже на то, как работает труба | работает в 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 передача всех функций в качестве параметров.Примером, который вы можете привести тест на скрипке было бы:

​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-запрос, приведет к увеличению сложности системы.Вместо этого у вас мог бы быть непрозрачный менеджер запросов, очень похожий на metaFunction у вас есть, и пусть он решает, будут ли данные передаваться локально или удаленно.

Вот такой образец 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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top