5

これは、stackoverflow に関する私の最初の投稿です。もし私が全くの愚痴のように出くわしたり、自分自身を完全に明確にすることができなくても、あまり激しく非難しないでください。:-)

これが私の問題です。最初の関数の完了を確認してから2番目の関数を実行することにより、2つの関数を別の関数に「結び付ける」javascript関数を作成しようとしています。

これに対する簡単な解決策は、スコープ内で両方の関数を呼び出すメタ関数を作成することです。ただし、最初の関数が非同期 (具体的には AJAX 呼び出し) で、2 番目の関数が最初の関数の結果データを必要とする場合、それはうまくいきません。

解決策の私のアイデアは、最初の関数に「フラグ」を与えることでした。つまり、呼び出されると、パブリックプロパティ「this.trigger」(「0」として初期化され、完了時に「1」に設定) を作成します。そうすることで、別の関数がフラグの値 ([0,1]) をチェックできるようになります。条件が満たされた場合 (「トリガー == 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

5 に答える 5

8

クロージャと呼ばれる 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は、顧客の請求額を受け入れ、AJAXget呼び出しは受け取った値を渡した関数に渡します。したがって、callbackは既に互換性があり、2 番目の AJAX 呼び出しのコールバックとして直接機能できます。したがって、これ自体が、2 つの非同期呼び出しを順番に結合し、それらを (外部から) 単一の非同期関数に見えるものにラップする例です。

次に、これを別の操作でチェーンできます。

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 人の加入者すべてに通話が発信されます。

于 2010-03-31T12:50:52.223 に答える
5

この「準備ができたら電話してください」という問題に対する決定的な答えは、callbackです。コールバックは基本的に、オブジェクト プロパティに割り当てる関数です ("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);  
}

もちろん、これは十分に柔軟ではありません。異なる 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);  
}

この 2 番目の関数は、次のように使用できます。

 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://....");

アイデアは、イベント ハンドラで「this」を「閉じる」(エイリアス化する) ことで、さらに渡せるようにします。

于 2010-03-31T12:52:33.933 に答える
1

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呼び出しを行います。キャッシュをきめ細かく制御したい場合は、3番目のパラメーターを渡して、データをキャッシュする期間を制御できます。

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
于 2010-03-31T17:57:11.253 に答える
1

私はそれを解決しましたが、今では完全にうまく機能しているようです。コードを整理したら、後で投稿します。それまでの間、ご協力いただきありがとうございました。

アップデート

Webkit (Safari、Chrome)、Mozilla、Opera でコードを試しました。うまく機能しているようです。返信をお待ちしております。

更新 2

TieExc() メソッドを変更して、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>
于 2010-04-01T11:40:01.727 に答える
0

非常に簡単な解決策は、最初の ajax 呼び出しを同期にすることです。これは、オプションのパラメーターの 1 つです。

于 2010-03-31T12:54:52.567 に答える