“分阶段” javascript中函数的执行
这是我在 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”来标记完成。如果是这样,我不知道如何解决这个问题。
无论如何,希望有人对这种特殊问题有想法或经验。
感谢您的阅读!
氟肯
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. :-)
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.
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.
My idea for a solution was to give the first function a "flag", i.e. making it create a public property "this.trigger" (initialized as "0", set to "1" upon completion) once it is called; doing that should make it possible for another function to check the flag for its value ([0,1]). If the condition is met ("trigger == 1") the second function should get called.
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:
<!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. 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". 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
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
您可以利用 JS 的一个称为“闭包”的功能。将其与称为“连续传递样式”的非常常见的 JS 模式相结合,您就得到了解决方案。 (这些东西都不是 JS 原创的,但在 JS 中大量使用)。
“连续传递风格”指的是回调。每个函数不返回值,而是调用回调(延续)并给出结果。
然后,您可以轻松地将两者联系在一起:
嵌套的匿名函数可以从它们所在的范围“查看”变量。因此无需使用特殊属性来传递信息。
更新
为了澄清,在您问题的代码片段中,很明显您大致是这样思考的:
(然后,作为一个复杂的因素,循环必须使用
setInterval
开始运行,并使用clearInterval
退出,以允许其他 JS 代码运行 - 但这基本上是一个“轮询”尽管如此循环”)。你不需要这样做!
不要让第一个函数在完成时设置属性,而是让它调用函数。
为了使这一点绝对清楚,让我们重构您的原始代码:
[更新 2:顺便说一句,那里有一个错误。您将
trigger
属性的当前值复制到名为trigger
的新局部变量中。然后最后将 1 赋给该局部变量。没有其他人能够看到这一点。局部变量是函数私有的。 但是你不需要做任何这些事情,所以继续阅读...]我们所要做的就是告诉该函数在完成后调用什么,并摆脱该属性 -设置:
现在不需要“调用检查”或特殊的
tieExc
帮助程序。您可以使用很少的代码轻松地将两个函数绑定在一起。这样做的另一个优点是我们可以将不同的参数传递给第二个函数。通过您的方法,相同的参数将传递给两者。
例如,第一个函数可能会对 AJAX 服务进行几次调用(为简洁起见,使用 jQuery):
这里,
callback
接受客户账单金额,AJAXget
call 将接收到的值传递给我们传递的函数,因此回调已经兼容,因此可以直接充当第二个 AJAX 调用的回调。因此,这本身就是一个将两个异步调用按顺序绑定在一起并将它们包装在(从外部)看起来像单个异步函数的示例。然后我们可以将其与另一个操作链接起来:
或者我们可以(再次)使用匿名函数:
因此,通过像这样链接函数调用,每个步骤都可以在信息可用时立即将信息传递到下一步。
通过让函数在完成后执行回调,您可以摆脱每个函数内部工作方式的任何限制。它可以执行 AJAX 调用、计时器等等。只要向前传递“继续”回调,就可以有任意层的异步工作。
基本上,在异步系统中,如果您发现自己编写了一个循环来检查变量并查明它是否已更改状态,则说明某个地方出了问题。相反,应该有一种方法来提供一个在状态更改时调用的函数。
更新 3
我在评论中的其他地方看到您提到实际问题是缓存结果,所以我解释这一点的所有工作都是浪费时间。这是你应该在问题中提出的内容。
更新4
最近我写了一篇关于在 JavaScript 中缓存异步调用结果主题的简短博客文章。
(更新 4 结束)
共享结果的另一种方法是为一个回调提供一种向多个订阅者“广播”或“发布”的方法:
因此:
然后让一些缓慢的操作发布其结果:
当一个呼叫结束后,它现在将呼叫所有三个订阅者。
You can take advantage of a feature of JS called closure. Combine that with a very common JS pattern called "continuation passing style" and you have your solution. (Neither of these things are original to JS, but are heavily used in JS).
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:
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:
(And then as a complicating factor, the loop has to use
setInterval
to start running andclearInterval
to quit, to allow other JS code to run - but it's basically a "polling loop" nevertheless).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:
[Update 2: By the way, there's a bug there. You copy the current value of the
trigger
property into a new local variable calledtrigger
. Then at the end you assign 1 to that local variable. 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:
There's now no need for your "call-check" or your special
tieExc
helper. You can easily tie two functions together with very little code.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):
Here,
callback
accepts the customer bill amount, and the AJAXget
call passes the received value to the function we pass it, so thecallback
is already compatible and so can directly act as the callback for the second AJAX call. 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:
Or we could (again) have used an anonymous function:
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. 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
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
More recently I've written a short blog post on the subject of caching asynchronous call results in JavaScript.
(end of update 4)
Another way to share results is to provide a way for one callback to "broadcast" or "publish" to several subscribers:
So:
Then let some slow operation publish its results:
When that one call finishes, it will now call all three subscribers.
对于“准备好后给我打电话”问题的最终答案是回调。回调基本上是分配给对象属性的函数(例如“onload”)。当对象状态改变时,调用该函数。例如,此函数向给定的 url 发出 ajax 请求,并在完成后发出尖叫声:
当然,这不够灵活,因为我们可能希望针对不同的 ajax 调用执行不同的操作。幸运的是,javascript 是一种函数式语言,因此我们可以简单地将所需的操作作为参数传递:
第二个函数可以像这样使用:
根据注释,这里是一个如何在对象内使用 ajax 的示例
这个想法是“关闭” (别名)“this”在事件处理程序中,以便可以进一步传递。
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"). 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:
Of course, this is not flexible enough, because we presumably want different actions for different ajax calls. Fortunately, javascript is a functional language, so we can simply pass the required action as a parameter:
This second function can be used like this:
As per comments, here an example how to use ajax within an object
The idea is to "close" (aliased) "this" in the event handler, so that it can be passed further.
欢迎来到SO!顺便说一句,你给人的印象是一个十足的白痴,你的问题完全不清楚:)
这是建立在@Daniel关于使用延续的答案的基础上的。这是一个将多个方法链接在一起的简单函数。很像管道
|
在 unix 中的工作方式。它采用一组函数作为参数,这些函数将按顺序执行。每个函数调用的返回值都会作为参数传递给下一个函数。要使用它,请从
Chained
创建一个对象,并将所有函数作为参数传递。您可以在 fiddle 上测试的示例是:要将其与 AJAX 请求一起使用,请将链式函数传递为XMLHttpRequest 的成功回调。
重要的是每个函数都依赖于前一个函数的输出,因此您必须在每个阶段返回一些值。
这只是一个建议 - 负责检查数据是否在本地可用或必须发出 HTTP 请求将会增加系统的复杂性。相反,您可以拥有一个不透明的请求管理器,就像您拥有的
metaFunction
一样,并让它决定是在本地还是远程提供数据。这是一个 示例
Request
对象,它可以在没有任何其他对象或函数的情况下处理这种情况了解数据的来源:要使用它,您需要调用
Request.get(..)
,它会返回缓存数据(如果可用),否则会进行 AJAX 调用。如果您正在寻找对缓存的精细控制,则可以传递第三个参数来控制数据应缓存多长时间。Welcome to 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. It is a simple function that chains multiple methods together. Much like how the pipe
|
works in 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.To use it, create an object from
Chained
passing all functions as parameters. An example you can test on fiddle would be:To use it with an AJAX request, pass the chained function as the success callback to the XMLHttpRequest.
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. 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.Here is a sample
Request
object that handles this situation without any other objects or functions knowing where the data was served from:To use it, you would make a call to
Request.get(..)
, and it returns cached data if available or makes an AJAX call otherwise. 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.我已经解决了,现在看起来效果很好。稍后我整理完之后会发布我的代码。同时,非常感谢您的帮助!
更新
尝试了 Webkit(Safari、Chrome)、Mozilla 和 Opera 中的代码。似乎工作得很好。期待任何答复。
更新 2
我更改了 tieExc() 方法以集成 Anurag 的链式函数调用语法。现在,您可以通过将函数作为参数传递来在完成检查时调用任意数量的函数。
如果您不想阅读代码,请尝试:http://jsfiddle.net/UMuj3/ (顺便说一句,JSFiddle 是一个非常简洁的网站!)。
JS 代码:
HTML:
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. Seems to work just fine. Looking forward to any replies.
Update 2
I changed the tieExc() method to integrate Anurag's chained function call syntax. 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!).
JS-Code:
HTML:
一个非常简单的解决方案是使您的第一个 ajax 调用同步。它是可选参数之一。
A very simple solution would be to make your first ajax call synchronous. It's one of the optional parameters.