0

問題を解決するという点で、私はここで完成したばかりの完全に機能する解決策を持っています:

// synchronous dynamic script loading. 
// takes an array of js url's to be loaded in that specific order. 
// assembles an array of functions that are referenced more directly rather than 
// using only nested closures. I couldn't get it going with the closures and gave up on it. 

function js_load(resources, cb_done) {
    var cb_list = []; // this is not space optimal but nobody gives a damn 
    array_each(resources, function(r, i) {
        cb_list[i] = function() {
            var x = document.body.appendChild(document.createElement('script'));
            x.src = r;
            console.log("loading "+r);
            x.onload = function() { 
                console.log("js_load: loaded "+r); 
                if (i === resources.length-1) {
                    cb_done();
                } else {
                    cb_list[i+1]();
                }
            }; 
        };
    });
    cb_list[0]();
}

これは私が今望んでいることを実行し、おそらく最初のアプローチが成功した場合よりもはるかに簡単にデバッグできるため、これには完全に満足しています。

しかし、私が乗り越えられないのは、なぜそれを機能させることができなかったのかということです。

こんな感じでした。

function js_load(resources, cb_done) {
    var cur_cont = cb_done;
    // So this is an iterative approach that makes a nested "function stack" where 
    // the inner functions are hidden inside the closures. 
    array_each_reverse(resources, function(r) {
        // the stack of callbacks must be assembled in reverse order
        var tmp_f = function() {
            var x = document.body.appendChild(document.createElement('script'));
            x.src = r;
            console.log("loading "+r);
            x.onload = function() { console.log("js_load: loaded "+r); cur_cont(); }; // TODO: get rid of this function creation once we know it works right 
        };
        cur_cont = tmp_f; // Trying here to not make the function recursive. We're generating a closure with it inside. Doesn't seem to have worked :(
    });
    cur_cont();
}

とりわけ奇妙なことに、無限ループで自分自身を呼び出そうとし続けました。デバッグ中に、関数がどの関数であり、関数がその中に含まれているのかを識別するのは非常に困難です。

私はコードを掘り下げませんでしたが、jQuery.queueクロージャだけを使用するのではなく、(配列を使用して継続のキューを追跡する)作業中のメカニズムと同様のメカニズムも実装しているようです。

私の質問はこれです:それ自体が作成する関数をラップするクロージャを構築することによって、引数として関数を取り、他の関数のリストでそれを拡張できるJavascript関数を構築することは可能ですか?

これを説明するのは本当に難しいです。しかし、誰かがそれに対して適切な理論に裏付けられた数学用語を持っていると確信しています。

上記のコードで参照されているPSは、これらのルーチンです。

// iterates through array (which as you know is a hash), via a for loop over integers
// f receives args (value, index)
function array_each(arr, f) {
    var l = arr.length; // will die if you modify the array in the loop function. BEWARE
    for (var i=0; i<l; ++i) {
        f(arr[i], i);
    }
}

function array_each_reverse(arr, f) {
    var l = arr.length; // will die if you modify the array in the loop function. BEWARE
    for (var i=l-1; i>=0; --i) {
        f(arr[i], i);
    }
}
4

1 に答える 1

1

問題は、作成したすべてのcur_cont新しい関数の値をどのように設定cur_contし、onloadコールバックを呼び出すかです。のようなクロージャを作成すると、のようなtmp_f自由変数cur_contは現在の値に「凍結」されません。cur_contが変更された場合、内部からtmp_fの参照は、更新された新しい値を参照します。cur_cont作成したばかりの新しいtmp_f関数に絶えず変更しているため、他の関数への参照は失われます。その後、cur_contが実行されて終了すると、cur_contが再度呼び出されます。これは、実行を終了したばかりの関数とまったく同じです。したがって、無限ループになります。

この種の状況では、クロージャ内に自由変数の値を保持する必要があります。最も簡単な方法は、新しい関数を作成し、保持したい値でそれを呼び出すことです。この新しい関数を呼び出すことにより、その実行のためだけに新しい変数が作成され、必要な値が保持されます。

関数js_load(resources、cb_done){
    var cur_cont = cb_done;
    array_each_reverse(resources、function(r){
        //コールバックのスタックは逆の順序でアセンブルする必要があります

        //新しい関数を作成し、`cur_cont`の現在の値を渡します
        //変数であるため、後の実行で正しい値が得られます。
        //この関数内では、`cur_cont`の代わりに`done`を使用します。
        cur_cont =(function(done){

            //終了時に`done`を呼び出す新しい関数を作成し、それを返します。
            //この関数は新しい`cur_cont`になります。
            function()を返す{

                var x = document.body.appendChild(document.createElement('script'));
                x.src = r;
                console.log( "loading" + r);
                x.onload = function(){
                    console.log( "js_load:ロード済み" + r);
                    終わり();
                };
            };
        })(cur_cont);

    });

    //関数チェーンの実行を開始します
    cur_cont();
}

編集:実際には、これは関数を使用することでさらに簡単にすることができArray.reduceます。概念的には、配列を取得し、その配列から単一の関数を生成します。生成される連続する各関数は、最後に生成された関数に依存する必要があります。これは、reduceが解決に役立つように設計された問題です。

function js_load(resources, done) {
    var queue = resources.reduceRight(function(done, r) {
        return function() {
            var x = document.body.appendChild(document.createElement('script'));
            x.src = r;
            console.log("loading "+r);
            x.onload = function() {
                console.log("js_load: loaded "+r);
                done();
            };
        };
    }, done);

    queue();
};

reducereduceRightは古いブラウザ(<= IE8)では使用できないことに注意してください。JavaScriptの実装は、MDNページにあります。

于 2013-03-24T23:48:45.327 に答える