在javascript中"分阶段"执行函数

FK8*_*K82 5 javascript function execution phase

这是我关于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>
Run Code Online (Sandbox Code Playgroud)

用于测试的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>
Run Code Online (Sandbox Code Playgroud)

我很确定这是javascript范围的一些问题,因为我已经检查了触发器是否正确设置为"1"并且确实如此.很可能"checkCall()"函数没有收到更新的对象,而只是通过将"this.trigger"设置为"1"来检查它的旧实例,这显然从不标记完成.如果是这样,我不知道如何解决这个问题.

无论如何,希望有人对这种特殊问题有一个想法或经验.

谢谢阅读!

FK

Dan*_*ker 8

您可以利用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
}
Run Code Online (Sandbox Code Playgroud)

"延续传递风格"指的是回调.每个函数调用一个回调(延续)并给它结果,而不是返回一个值.

然后,您可以轻松地将两者结合在一起:

foo(input1, function(results1) {

    bar(results1, function(results2) {

        alert(results2);
    });
});
Run Code Online (Sandbox Code Playgroud)

嵌套的匿名函数可以"看到"它们所在范围内的变量.因此不需要使用特殊属性来传递信息.

更新

为了澄清,在您的问题的代码片段中,很明显您的思维大致如下:

我有一个长时间运行的异步操作,所以我需要知道它何时完成才能启动下一个操作.所以我需要将该状态视为属性.然后在其他地方我可以在循环中运行,反复检查该属性以查看它何时变为"已完成"状态,因此我知道何时继续.

(然后作为一个复杂的因素,循环必须用来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) ;
}
Run Code Online (Sandbox Code Playgroud)

[ 更新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) ;
}
Run Code Online (Sandbox Code Playgroud)

现在不需要你的"电话检查"或你的特别tieExc助手.只需很少的代码就可以轻松地将两个函数绑定在一起.

var mySpan = "#myspan";

cllFnc(mySpan, function() { rcvFnc(mySpan); });
Run Code Online (Sandbox Code Playgroud)

这样做的另一个好处是我们可以将不同的参数传递给第二个函数.使用您的方法,相同的参数将传递给两者.

例如,第一个函数可能会对AJAX服务进行几次调用(为简洁起见使用jQuery):

function getCustomerBillAmount(name, callback) {

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

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

    });
}
Run Code Online (Sandbox Code Playgroud)

在这里,callback接受客户账单金额,并且AJAX get调用将接收到的值传递给我们传递的函数,因此callback它已经兼容,因此可以直接充当第二次AJAX调用的回调.因此,这本身就是将两个异步调用按顺序连接在一起并将它们包含在(从外部)出现的单个异步函数中的示例.

然后我们可以用另一个操作来链接它:

function displayBillAmount(amount) {

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

getCustomerBillAmount("Simpson, Homer J.", displayBillAmount);
Run Code Online (Sandbox Code Playgroud)

或者我们可以(再次)使用匿名函数:

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

    $("#billAmount").text(amount); 
});
Run Code Online (Sandbox Code Playgroud)

因此,通过链接这样的函数调用,每个步骤可以在信息可用时立即将信息传递到下一步.

通过使函数在完成后执行回调,您可以免除对每个函数内部工作方式的任何限制.它可以做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);
            }
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

所以:

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);
});
Run Code Online (Sandbox Code Playgroud)

然后让一些慢速操作发布其结果:

lookupTaxRecords("Homer J. Simpson", finished.publish);
Run Code Online (Sandbox Code Playgroud)

当一个呼叫结束时,它现在将呼叫所有三个用户.

  • 如果所有你从中获取的是一个错误修复,但你仍然认为轮询状态的改变是一个好主意,那么我没有解释*任何有用的东西*.真正具有讽刺意味的是,您正在使用异步回调来检查状态变化! (2认同)

use*_*291 5

这个问题的最终答案是"当你准备就绪时给我打电话"问题是一个回调.回调基本上是您分配给对象属性的函数(如"onload").当对象状态改变时,调用该函数.例如,此函数对给定的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);  
}
Run Code Online (Sandbox Code Playgroud)

当然,这不够灵活,因为我们可能想要针对不同的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);  
}
Run Code Online (Sandbox Code Playgroud)

第二个函数可以像这样使用:

 ajax("http://...", function(text) {
      do something with ajax response  
 });
Run Code Online (Sandbox Code Playgroud)

根据评论,这里是一个如何在对象中使用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://....");
Run Code Online (Sandbox Code Playgroud)

想法是在事件处理程序中"关闭"(别名)"this",以便可以进一步传递它.


FK8*_*K82 1

我已经解决了,现在看起来效果很好。稍后我整理完之后会发布我的代码。同时,非常感谢您的帮助!

\n\n

更新

\n\n

尝试了 Webkit(Safari、Chrome)、Mozilla 和 Opera 中的代码。似乎工作得很好。期待任何答复。

\n\n

更新2

\n\n

我更改了 tieExc() 方法以集成 Anurag 的链式函数调用语法。现在,您可以通过将函数作为参数传递来在完成检查时调用任意数量的函数。

\n\n

如果您不想阅读代码,请尝试: http: //jsfiddle.net/UMuj3/(顺便说一句,JSFiddle 是一个非常简洁的网站!)。

\n\n

JS 代码:

\n\n
/**/function meta() {\n\nvar myMeta = this ;\n\n/**  **/this.cllFnc = function(tgt,lgt) { //!! first function\n\n    this.trigger = 0 ;  //!! status flag, initially zero\n    var that = this ;   //!! required to access parent scope from inside nested function\n\n    var _tgt = document.getElementById(tgt) ; //!! changes the color of the target div to signalize the function\'s execution\n    _tgt.style.background = \'#66f\' ;\n\n    alert(\'Calling! ...\') ;\n\n    setTimeout(function() { //!! simulates longer AJAX call, duration 5000ms\n\n        that.trigger = 1 ;  //!! status flag, one upon completion\n\n    },5000) ;\n\n} ;\n\n/**  **/this.rcvFnc = function(tgt) { //!! second function that should get called upon the first function\'s completion\n\n    var _tgt = document.getElementById(tgt) ; //!! changes color of the target div to signalize the function\'s execution\n    _tgt.style.background = \'#f63\' ;\n\n    alert(\'... Someone picked up!\') ;\n\n} ;\n\n/**  **/this.callCheck = function(obj) {    \n\n    return (obj.trigger == 1)   ?   true\n        :   false\n        ;\n\n} ;\n\n/**  **/this.tieExc = function() {\n\n    var functions = arguments ;\n\n    var myItv = window.setInterval(function() {\n\n        document.getElementById(\'target\').innerHTML = new Date() ; //!! displays date in target div to signalize the interval increments\n\n        var myCallCheck = myMeta.callCheck(functions[0]) ; //!! checks property "trigger"\n\n        if(myCallCheck == true) { \n\n            clearInterval(myItv) ;\n\n            for(var n=1; n < functions.length; n++) {\n\n                functions[n].call() ;\n\n            }\n\n        } else if(myCallCheck == false) { \n            return ;\n        }\n\n    },100) ;\n\n\n\n} ;\n\n}\xe2\x80\x8b\n
Run Code Online (Sandbox Code Playgroud)\n\n

HTML

\n\n
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/strict.dtd >\n\n<html>\n\n<head>\n\n    <script type=\'text/javascript\'  >\n        <!-- see above -->\n    </script>\n    <title>\n\n      Javascript Phased Execution Test Page\n\n    </title>\n\n</head>\n\n<body>\n\n        <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 ;\' >\n            Test Div\n        </div>\n\n        <div style="float:left;" >\n            <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 />\n        </div>\n\n<body>\n\n\n</html>\n
Run Code Online (Sandbox Code Playgroud)\n