JavaScript での関数の「段階的」実行
-
23-09-2019 - |
質問
これは私にとって stackoverflow への最初の投稿です。もし私がまったくの無知であると思われても、あるいは私が自分のことを完全に明確にできなくても、あまり激しく非難しないでください。:-)
これが私の問題です:最初の関数の完了を確認してから 2 番目の関数を実行することで、2 つの関数を別の関数に「結び付ける」JavaScript 関数を作成しようとしています。
これに対する簡単な解決策は、明らかに、スコープ内の両方の関数を呼び出すメタ関数を作成することです。ただし、最初の関数が非同期 (具体的には AJAX 呼び出し) で、2 番目の関数が最初の関数の結果データを必要とする場合、それは機能しません。
私の解決策のアイデアは、最初の関数に「フラグ」を与えることでした。呼び出されると、パブリック プロパティ "this.trigger" ("0" として初期化され、完了時に "1" に設定される) を作成します。そうすることで、別の関数がフラグの値 ([0,1]) をチェックできるようになります。条件が満たされた場合 (「trigger == 1」)、2 番目の関数が呼び出されます。
以下は、私がテストに使用した抽象的なコード例です。
<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>
トリガーが正しく「1」に設定されているかどうかを確認したところ、実際に設定されているため、これはJavaScriptのスコープに関する問題であると確信しています。おそらく、「checkCall()」関数は更新されたオブジェクトを受け取らず、その代わりにその古いインスタンスをチェックするだけですが、明らかに「this.trigger」を「1」に設定することで完了フラグを立てることはありません。もしそうなら、その問題にどう対処すればいいのかわかりません。
とにかく、誰かがこの種の問題についてアイデアや経験を持っていることを願っています。
読んでくれてありがとう!
FK
解決 4
私はそれを解決しました、そして今では完全にうまく機能しているようです。コードを整理した後、後で投稿します。それまでの間、ご協力いただき誠にありがとうございました。
アップデート
Webkit (Safari、Chrome)、Mozilla、Opera でコードを試しました。問題なく動作しているようです。ご返信をお待ちしております。
アップデート 2
Anurag の連鎖関数呼び出し構文を統合するために、tieExc() メソッドを変更しました。完了チェック時に関数を引数として渡すことで、必要なだけ関数を呼び出すことができるようになりました。
コードを読む気がない場合は、試してみてください。 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 の機能を利用できます。これを「継続渡しスタイル」と呼ばれる非常に一般的な 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
}
「継続渡しスタイル」とはコールバックを指します。各関数は値を返す代わりにコールバック (継続) を呼び出し、結果を返します。
その後、この 2 つを簡単に結び付けることができます。
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
ヘルパー。非常に少ないコードで 2 つの関数を簡単に結び付けることができます。
var mySpan = "#myspan";
cllFnc(mySpan, function() { rcvFnc(mySpan); });
このもう 1 つの利点は、2 番目の関数に異なるパラメーターを渡すことができることです。あなたのアプローチでは、同じパラメータが両方に渡されます。
たとえば、最初の関数は、AJAX サービスへの呼び出しをいくつか実行します (簡潔にするために jQuery を使用します)。
function getCustomerBillAmount(name, callback) {
$.get("/ajax/getCustomerIdByName/" + name, function(id) {
$.get("/ajax/getCustomerBillAmountById/" + id), callback);
});
}
ここ、 callback
顧客の請求金額と AJAX を受け入れます。 get
呼び出しは受け取った値を関数に渡します。 callback
はすでに互換性があるため、2 番目の AJAX 呼び出しのコールバックとして直接機能できます。したがって、これ自体は 2 つの非同期呼び出しを順番に結合し、(外側からは) 1 つの非同期関数のように見えるものでそれらをラップする例です。
次に、これを別の操作と連鎖させることができます。
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終了)
結果を共有するもう 1 つの方法は、1 つのコールバックで複数のサブスクライバーに「ブロードキャスト」または「パブリッシュ」する方法を提供することです。
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);
その 1 つの通話が終了すると、3 人の加入者すべてに電話をかけることになります。
これまで決定的な答えは「準備ができたら私を呼んで、」問題は、のコールバックのです。コールバックを使用すると、(「オンロード」などの)オブジェクトのプロパティに割り当てることを基本的機能です。オブジェクトの状態が変化したとき、関数が呼び出されます。たとえば、この関数は、指定した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);
}
我々は、おそらく別の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
});
ここでコメントを1として、オブジェクト
内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へようこそ!ところで、あなたは合計とんちきとして渡って来て、あなたの質問は全く不明である。)
これは継続を使ってのダニエルの答え@上に構築しています。それは一緒にチェーン複数の方法という単純な機能です。多くのパイプ|
は、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
のように、不透明な要求マネージャーを持っており、データはローカルまたはリモートで提供される場合、それは決定させることができます。
ここでA サンプル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
Aの非常に簡単な解決策は、あなたの最初のAJAX呼び出しを同期させることであろう。これは、オプションのパラメータの一つだ。