简体   繁体   English

在javascript中“分阶段”执行函数

[英]“Phased” execution of functions in javascript

This is my first post on stackoverflow, so please don't flame me too hard if I come across like a total nitwit or if I'm unable ot make myself perfectly clear. 这是我关于stackoverflow的第一篇文章,所以如果我遇到像一个完全的傻瓜,或者如果我不能让自己完全清楚,请不要太过刻板。 :-) :-)

Here's my problem: I'm trying to write a javascript function that "ties" two functions to another by checking the first one's completion and then executing the second one. 这是我的问题:我正在尝试编写一个javascript函数,通过检查第一个函数的完成然后执行第二个函数将两个函数“绑定”到另一个函数。

The easy solution to this obviously would be to write a meta function that calls both functions within it's scope. 对此的简单解决方案显然是编写一个元函数,在其范围内调用两个函数。 However, if the first function is asynchronous (specifically an AJAX call) and the second function requires the first one's result data, that simply won't work. 但是,如果第一个函数是异步的(特别是一个AJAX调用)而第二个函数需要第一个函数的结果数据,那么这根本不起作用。

My idea for a solution was to give the first function a "flag", ie making it create a public property "this.trigger" (initialized as "0", set to "1" upon completion) once it is called; 我对解决方案的想法是给第一个函数一个“标志”,即一旦调用它就创建一个公共属性“this.trigger”(初始化为“0”,在完成时设置为“1”); doing that should make it possible for another function to check the flag for its value ([0,1]). 这样做可以使另一个函数检查标志的值([0,1])。 If the condition is met ("trigger == 1") the second function should get called. 如果满足条件(“trigger == 1”),则应调用第二个函数。

The following is an abstract example code that I have used for testing: 以下是我用于测试的抽象示例代码:

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

The HTML part for testing: 用于测试的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>

I'm pretty sure that this is some issue with javascript scope as I have checked whether the trigger gets set to "1" correctly and it does. 我很确定这是javascript范围的一些问题,因为我已经检查了触发器是否正确设置为“1”并且确实如此。 Very likely the "checkCall()" function does not receive the updated object but instead only checks its old instance which obviously never flags completion by setting "this.trigger" to "1". 很可能“checkCall()”函数没有收到更新的对象,而只是通过将“this.trigger”设置为“1”来检查它的旧实例,这显然从不标记完成。 If so I don't know how to address that issue. 如果是这样,我不知道如何解决这个问题。

Anyway, hope someone has an idea or experience with this particular kind of problem. 无论如何,希望有人对这种特殊问题有一个想法或经验。

Thanks for reading! 谢谢阅读!

FK FK

You can take advantage of a feature of JS called closure. 您可以利用JS的一个名为closure的功能。 Combine that with a very common JS pattern called "continuation passing style" and you have your solution. 将它与一个非常常见的JS模式结合起来,称为“延续传递风格”,你就有了解决方案。 (Neither of these things are original to JS, but are heavily used in 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
}

The "continuation passing style" refers to the callback. “延续传递风格”指的是回调。 Instead of returning a value, each function calls a callback (the continuation) and gives it the results. 每个函数调用一个回调(延续)并给它结果,而不是返回一个值。

You can then tie the two together easily: 然后,您可以轻松地将两者结合在一起:

foo(input1, function(results1) {

    bar(results1, function(results2) {

        alert(results2);
    });
});

The nested anonymous functions can "see" variables from the scope they live in. So there's no need to use special properties to pass information around. 嵌套的匿名函数可以“看到”它们所在范围内的变量。因此不需要使用特殊属性来传递信息。

Update 更新

To clarify, in your question's code snippet, it's clear that you are thinking roughly like this: 为了澄清,在您的问题的代码片段中,很明显您的思维大致如下:

I have a long-running asynchronous operation, so I need to know when it finishes in order to start the next operation. 我有一个长时间运行的异步操作,所以我需要知道它何时完成才能开始下一个操作。 So I need to make that state visible as a property. 所以我需要将该状态视为属性。 Then elsewhere I can run in a loop, repeatedly examining that property to see when it changes to the "completed" state, so I know when to continue. 然后在其他地方我可以在循环中运行,反复检查该属性以查看它何时变为“已完成”状态,因此我知道何时继续。

(And then as a complicating factor, the loop has to use setInterval to start running and clearInterval to quit, to allow other JS code to run - but it's basically a "polling loop" nevertheless). (然后作为一个复杂的因素,循环必须使用setInterval开始运行和clearInterval退出,以允许其他JS代码运行 - 但它基本上是一个“轮询循环”)。

You do not need to do that! 你不需要这样做!

Instead of making your first function set a property on completion, make it call a function. 不要让你的第一个函数在完成时设置属性,而是让它调用一个函数。

To make this absolutely clear, let's refactor your original code: 为了使这个绝对清楚,让我们重构您的原始代码:

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

[ Update 2 : By the way, there's a bug there. [ 更新2 :顺便说一下,那里有一个错误。 You copy the current value of the trigger property into a new local variable called trigger . trigger属性的当前值复制到名为trigger的新局部变量中。 Then at the end you assign 1 to that local variable. 然后在结束时为该局部变量赋值1。 No one else is going to be able to see that. 没有人能够看到这一点。 Local variables are private to a function. 局部变量是函数的私有变量。 But you don't need to do any of this anyway, so keep reading... ] 但是你无论如何都不需要这样做,所以继续阅读... ]

All we have to do is tell that function what to call when it's done, and get rid of the property-setting: 我们所要做的就是告诉该函数在完成后调用什么,并摆脱属性设置:

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

There's now no need for your "call-check" or your special tieExc helper. 现在不需要你的“电话检查”或你的特殊tieExc助手。 You can easily tie two functions together with very little code. 只需很少的代码就可以轻松地将两个函数绑定在一起。

var mySpan = "#myspan";

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

Another advantage of this is that we can pass different parameters to the second function. 这样做的另一个好处是我们可以将不同的参数传递给第二个函数。 With your approach, the same parameters are passed to both. 使用您的方法,相同的参数将传递给两者。

For example, the first function might do a couple of calls to an AJAX service (using jQuery for brevity): 例如,第一个函数可能会对AJAX服务进行几次调用(为简洁起见使用jQuery):

function getCustomerBillAmount(name, callback) {

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

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

    });
}

Here, callback accepts the customer bill amount, and the AJAX get call passes the received value to the function we pass it, so the callback is already compatible and so can directly act as the callback for the second AJAX call. 这里, callback接受客户账单金额,并且AJAX get调用将接收到的值传递给我们传递的函数,因此callback已经兼容,因此可以直接充当第二次AJAX调用的回调。 So this is itself an example of tying two asynchronous calls together in sequence and wrapping them in what appears (from the outside) to be a single asynchronous function. 因此,这本身就是将两个异步调用按顺序连接在一起并将它们包含在(从外部)出现的单个异步函数中的示例。

Then we can chain this with another operation: 然后我们可以用另一个操作来链接它:

function displayBillAmount(amount) {

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

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

Or we could (again) have used an anonymous function: 或者我们可以(再次)使用匿名函数:

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

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

So by chaining function calls like this, each step can pass information forward to the next step as soon as it is available. 因此,通过链接这样的函数调用,每个步骤可以在信息可用时立即将信息传递到下一步。

By making functions execute a callback when they're done, you are freed from any limitations to how each functions works internally. 通过使函数在完成后执行回调,您可以免除对每个函数内部工作方式的任何限制。 It can do AJAX calls, timers, whatever. 它可以做AJAX调用,定时器等等。 As long as the "continuation" callback is passed forward, there can be any number of layers of asynchronous work. 只要向前传递“延续”回调,就可以有任意数量的异步工作层。

Basically, in an asynchronous system, if you ever find yourself writing a loop to check a variable and find out if it has changed state, then something has gone wrong somewhere. 基本上,在异步系统中,如果你发现自己编写一个循环来检查一个变量并找出它是否已经改变状态,那么某些地方就出现了问题。 Instead there should be a way to supply a function that will be called when the state changes. 相反,应该有一种方法来提供一个在状态改变时调用的函数。

Update 3 更新3

I see elsewhere in comments you mention that the actual problem is caching results, so all my work explaining this was a waste of time. 我在评论的其他地方看到你提到实际问题是缓存结果,所以我所有解释这个的工作都浪费时间。 This is the kind of thing you should put in the question. 这是你应该提出的问题。

Update 4 更新4

More recently I've written a short blog post on the subject of caching asynchronous call results in JavaScript . 最近我写了一篇关于在JavaScript中缓存异步调用结果的短篇博客文章

(end of update 4) (更新4结束)

Another way to share results is to provide a way for one callback to "broadcast" or "publish" to several subscribers: 另一种分享结果的方法是为一个回调提供一种方式,以“广播”或“发布”给多个订阅者:

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

So: 所以:

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

Then let some slow operation publish its results: 然后让一些慢速操作发布其结果:

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

When that one call finishes, it will now call all three subscribers. 当一个呼叫结束时,它现在将呼叫所有三个用户。

The definitive answer to this "call me when you're ready" problem is a callback . 这个问题的最终答案是“当你准备就绪时给我打电话”问题是一个回调 A callback is basically a function that you assign to an object property (like "onload"). 回调基本上是您分配给对象属性的函数(如“onload”)。 When object state changes, the function is called. 当对象状态改变时,调用该函数。 For example, this function makes an ajax request to the given url and screams when it's complete: 例如,此函数对给定的URL发出ajax请求,并在完成时发出尖叫声:

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

Of course, this is not flexible enough, because we presumably want different actions for different ajax calls. 当然,这不够灵活,因为我们可能想要针对不同的ajax调用采取不同的操作。 Fortunately, javascript is a functional language, so we can simply pass the required action as a parameter: 幸运的是,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);  
}

This second function can be used like this: 第二个函数可以像这样使用:

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

As per comments, here an example how to use ajax within an object 根据评论,这里是一个如何在对象中使用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://....");

The idea is to "close" (aliased) "this" in the event handler, so that it can be passed further. 想法是在事件处理程序中“关闭”(别名)“this”,以便可以进一步传递它。

Welcome to SO! 欢迎来到SO! Btw, You come across as a total nitwit and your question is totally unclear :) 顺便说一下,你遇到的是一个完整的傻瓜,你的问题完全不清楚:)

This is building upon @Daniel's answer of using continuations. 这是基于@Daniel使用continuation的答案。 It is a simple function that chains multiple methods together. 这是一个将多个方法链接在一起的简单函数。 Much like how the pipe | 很像管道如何| works in unix. 在unix中工作。 It takes a set of functions as its arguments which are to be executed sequentially. 它需要一组函数作为其顺序执行的参数。 The return value of each function call is passed on to the next function as a parameter. 每个函数调用的返回值作为参数传递给下一个函数。

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

To use it, create an object from Chained passing all functions as parameters. 要使用它,请从Chained创建一个对象,将所有函数作为参数传递。 An example you can test on fiddle would be: 您可以在小提琴上测试的一个例子是:

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

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

​To use it with an AJAX request, pass the chained function as the success callback to the XMLHttpRequest. 要将它与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);  

The important thing is that each function depends on the output of the previous function, so you must return some value at each stage. 重要的是每个函数都依赖于前一个函数的输出,因此必须在每个阶段返回一些值。


This is just a suggestion - giving the responsibility of checking whether data is available locally or an HTTP request must be made is going to increase the complexity of the system. 这只是一个建议 - 负责检查数据是否在本地可用或必须进行HTTP请求会增加系统的复杂性。 Instead, you could have an opaque request manager, much like the metaFunction you have, and let it decide if the data is to be served locally or remotely. 相反,您可以拥有一个不透明的请求管理器,就像您拥有的metaFunction一样,让它决定是在本地还是远程提供数据。

Here is a sample Request object that handles this situation without any other objects or functions knowing where the data was served from: 下面是一个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);
    }
};

To use it, you would make a call to Request.get(..) , and it returns cached data if available or makes an AJAX call otherwise. 要使用它,您可以调用Request.get(..) ,如果可用则返回缓存数据,否则进行AJAX调用。 A third parameter could be passed to control how long the data should be cached for, if you're looking for granular control over 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

I've worked it out and it seems to work perfectly well now. 我已经解决了它现在看起来效果很好。 I will post my code later after I have sorted it out. 在我将其整理出来后,我会稍后发布我的代码。 In the meantime, thanks a lot for you assistance! 在此期间,非常感谢您的帮助!

Update 更新

Tried the code in Webkit (Safari, Chrome), Mozilla and Opera. 尝试了Webkit(Safari,Chrome),Mozilla和Opera中的代码。 Seems to work just fine. 似乎工作得很好。 Looking forward to any replies. 期待任何回复。

Update 2 更新2

I changed the tieExc() method to integrate Anurag's chained function call syntax. 我更改了tieExc()方法以集成Anurag的链式函数调用语法。 Now you can call as many functions as you want upon completion check by passing them as arguments. 现在,您可以通过将它们作为参数传递,在完成检查时调用任意数量的函数。

If you are not inclined to read the code, try it: http://jsfiddle.net/UMuj3/ (btw, JSFiddle is a really neat site!). 如果您不想阅读代码,请尝试: http//jsfiddle.net/UMuj3/ (顺便说一下,JSFiddle是一个非常简洁的网站!)。

JS-Code: 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 : 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>

A very simple solution would be to make your first ajax call synchronous. 一个非常简单的解决方案是使您的第一个ajax调用同步。 It's one of the optional parameters. 这是可选参数之一。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM