web-dev-qa-db-ja.com

順次Javascript実行を強制する方法は?

クラス、イベントハンドラー、コールバックを含むかなり複雑な答えを見つけました(これはやや大胆なアプローチのようです)。コールバックは便利かもしれませんが、これらを最も単純なコンテキストで適用することはできません。この例を参照してください。

<html>
  <head>
    <script type="text/javascript">
      function myfunction()  {
        longfunctionfirst();
        shortfunctionsecond();
      }

      function longfunctionfirst() {
        setTimeout('alert("first function finished");',3000);
      }

      function shortfunctionsecond() {
        setTimeout('alert("second function finished");',200);
      }
    </script>
  </head>
  <body>
    <a href="#" onclick="javascript:myfunction();return false;">Call my function</a>
  </body>
</html>

この場合、2番目の関数は最初の関数の前に完了します。最初の関数が完了するまで、2番目の関数に実行を遅延させる最も簡単な方法は何ですか(または、1つありますか?)。

---編集---

だからそれはゴミの例でしたが、デビッド・ヘドランドのおかげで、この新しい例では実際に同期していることがわかります(テストプロセスでブラウザがクラッシュする!):

<html>
<head>

<script type="text/javascript">
function myfunction() {
    longfunctionfirst();
    shortfunctionsecond();
}

function longfunctionfirst() {
    var j = 10000;
    for (var i=0; i<j; i++) {
        document.body.innerHTML += i;
    }
    alert("first function finished");
}

function shortfunctionsecond() {
    var j = 10;
    for (var i=0; i<j; i++) {
        document.body.innerHTML += i;
    }
    alert("second function finished");
}
</script>

</head>

<body>
  <a href="#" onclick="javascript:myfunction();return false;">Call my function</a>
</body>
</html>

私の実際の問題はjQueryであり、IE自分自身がどこにも行けない場合、それについて別の質問を投稿する必要があります!

62
Tom

さて、setTimeoutは、その定義により、スレッドを保持しません。これが望ましいのは、もしそうなら、それが待っていた間、UI全体をフリーズするからです。本当にsetTimeoutを使用する必要がある場合は、コールバック関数を使用する必要があります。

function myfunction() {
    longfunctionfirst(shortfunctionsecond);
}

function longfunctionfirst(callback) {
    setTimeout(function() {
        alert('first function finished');
        if(typeof callback == 'function')
            callback();
    }, 3000);
};

function shortfunctionsecond() {
    setTimeout('alert("second function finished");', 200);
};

setTimeoutを使用してnotであるが、非常に長い間実行する関数があり、setTimeoutを使用していた場合それをシミュレートするには、関数が実際には同期であり、この問題はまったくありません。ただし、AJAXリクエストは非同期であり、setTimeoutと同様に、終了するまでUIスレッドを保持しないことに注意してください。 setTimeout、コールバックを操作する必要があります。

45
David Hedlund

私はきれいな解決策だと思うものを見つけるのに長い時間がかかったので、この質問に戻っています:私が知っているjavascriptシーケンシャル実行を強制する唯一の方法は約束を使用することです。約束の徹底的な説明があります: Promises/A および Promises/A +

私が知っている約束を実装する唯一のライブラリはjqueryなので、ここでjquery promiseを使用して質問を解決する方法を示します。

<html>
<head>
    <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
    <script type="text/javascript">
    function myfunction()
    {
        promise = longfunctionfirst().then(shortfunctionsecond);
    }
    function longfunctionfirst()
    {
        d = new $.Deferred();
        setTimeout('alert("first function finished");d.resolve()',3000);
        return d.promise()
    }
    function shortfunctionsecond()
    {
        d = new $.Deferred();
        setTimeout('alert("second function finished");d.resolve()',200);
        return d.promise()
    }
    </script>
</head>
<body>
    <a href="#" onclick="javascript:myfunction();return false;">Call my function</a>
</body>
</html>

Promiseを実装し、.then()で関数をチェーンすることにより、最初の関数が実行された後にのみ2番目の関数が実行されるようにします。longfunctionfirst()のコマンドd.resolve()は、関数。

技術的には、shortfunctionsecond()は遅延オブジェクトを作成してプロミスを返す必要はありませんが、私はプロミスに夢中になり、プロミスですべてを実装する傾向があります。

30
Raymond

私はプログラミングの古い手で、最近私の昔の情熱に戻って、このオブジェクト指向のイベント駆動型の明るい新しい世界に収まるのに苦労しています.Javascriptの非シーケンシャル動作の利点を見ている間、シンプルさと再利用性の方法で。私が取り組んだ簡単な例は、写真(javascript、HTML、phonegapなどでプログラムされた携帯電話)を撮り、サイズを変更してWebサイトにアップロードすることでした。理想的なシーケンスは次のとおりです。

  1. 写真を撮る
  2. 写真をimg要素に読み込む
  3. 画像のサイズを変更する(Pixasticを使用)
  4. Webサイトにアップロードする
  5. 成功の失敗についてユーザーに通知する

各ステップが終了時に次のステップに制御を戻す場合、これはすべて非常に単純な順次プログラムになりますが、実際には:

  1. 写真を撮るのは非同期なので、プログラムはそれが存在する前にimg要素にロードしようとします
  2. 写真の読み込みは非同期であるため、画像が完全に読み込まれる前に画像のサイズ変更が開始されます
  3. サイズ変更は非同期なので、画像が完全にサイズ変更される前にWebサイトへのアップロードが開始されます
  4. Webサイトへのアップロードはasynなので、写真が完全にアップロードされる前にプログラムが続行されます。

また、5つのステップのうち4つはコールバック関数に関係しています。

したがって、私の解決策は、前のステップで各ステップをネストし、.onloadおよび他の同様の戦略を使用することです。これは次のようになります。

takeAPhoto(takeaphotocallback(photo) {
  photo.onload = function () {
    resizePhoto(photo, resizePhotoCallback(photo) {
      uploadPhoto(photo, uploadPhotoCallback(status) {
        informUserOnOutcome();
      });
    }); 
  };
  loadPhoto(photo);
});

(私はコードをそれをもたらすのにあまり多くの間違いをしなかったことを願っていますが、本物は気が散りすぎています)

これは、非同期が良くなく、同期が良い完璧な例だと思います。なぜなら、Uiイベント処理とは逆に、次のステップを実行する前に各ステップを終了しなければならないからです。コードの再利用性は、すべてのネストのために、各コンテナに順番に渡さずに、または邪悪なグローバル変数を使用せずに、必要なすべてのパラメーターを内部関数に渡すのが難しいため、達成するのが困難です。このコードはリターンコードを提供しますが、リターンコードが使用可能になる前に最初のコンテナが終了します。

さて、トムの最初の質問に戻ると、15年前にlet say Cとダム電子ボードを使用していた非常にシンプルなプログラムに対するスマートで読みやすく、再利用しやすいソリューションは何でしょうか?

実際、要件は非常に単純であるため、Javsascriptと最新のプログラミングの基本的な理解を逃す必要があるという印象があります。確かに、技術は生産性を高めることを目的としています。

お待ち頂きまして、ありがとうございます

レイモンド恐竜;-)

11
Raymond

私はコールバックの方法を試してみましたが、これを機能させることができませんでした。理解しておかなければならないのは、実行はできなくても値はアトミックであることです。例えば:

alert('1'); <---これらの2つの関数は同時に実行されます

alert('2'); <---これらの2つの関数は同時に実行されます

しかし、このようにすることで、実行の順序を知る必要があります。

loop=2;
total=0;
for(i=0;i<loop;i++) {
           total+=1;
           if(total == loop)
                      alert('2');
           else
                      alert('1');
}
1
gamadril

JavaScriptでは、コードを待機させる方法はありません。私はこの問題を抱えており、その方法はサーバーへの同期SJAX呼び出しを行い、サーバーは実際にスリープを実行するか、戻る前に何らかのアクティビティを実行し、すべての時間、jsは待機します。

例:同期AJAX: http://www.hunlock.com/blogs/Snippets:_Synchronous_AJAX

1
rampr

あなたの例では、最初の関数は、2番目の関数が開始される前に実際に完了します。 setTimeoutは、タイムアウトに達するまで関数の実行を保留せず、単にバックグラウンドでタイマーを開始し、指定された時間後にアラートステートメントを実行します。

JavaScriptで「スリープ」を行うネイティブな方法はありません。時間をチェックするループを書くこともできますが、それはクライアントに大きな負担をかけます。また、Emacsianが説明したようにSynchronous AJAX呼び出しを行うこともできますが、それによりサーバーに余分な負荷がかかります。 setTimeoutの仕組みを理解しています。

1
Franz

純粋なJavascriptを使用しない場合は、 Livescript でシーケンシャルコードをビルドできます。これは見た目が良いです。 この例 をご覧ください。

# application
do
    i = 3
    console.log td!, "start"
    <- :lo(op) ->
        console.log td!, "hi #{i}"
        i--
        <- wait-for \something
        if i is 0
            return op! # break
        lo(op)
    <- sleep 1500ms
    <- :lo(op) ->
        console.log td!, "hello #{i}"
        i++
        if i is 3
            return op! # break
        <- sleep 1000ms
        lo(op)
    <- sleep 0
    console.log td!, "heyy"

do
    a = 8
    <- :lo(op) ->
        console.log td!, "this runs in parallel!", a
        a--
        go \something
        if a is 0
            return op! # break
        <- sleep 500ms
        lo(op)

出力:

0ms : start
2ms : hi 3
3ms : this runs in parallel! 8
3ms : hi 2
505ms : this runs in parallel! 7
505ms : hi 1
1007ms : this runs in parallel! 6
1508ms : this runs in parallel! 5
2009ms : this runs in parallel! 4
2509ms : hello 0
2509ms : this runs in parallel! 3
3010ms : this runs in parallel! 2
3509ms : hello 1
3510ms : this runs in parallel! 1
4511ms : hello 2
4511ms : heyy
1
ceremcem

私は同じ問題を抱えていました、これは私の解決策です:

var functionsToCall = new Array();

function f1() {
    $.ajax({
        type:"POST",
        url: "/some/url",
        success: function(data) {
            doSomethingWith(data);
            //When done, call the next function..
            callAFunction("parameter");
        }
    });
}

function f2() {
    /*...*/
    callAFunction("parameter2");
}
function f3() {
    /*...*/
    callAFunction("parameter3");
}
function f4() {
    /*...*/
    callAFunction("parameter4");
}
function f5() {
    /*...*/
    callAFunction("parameter5");
}
function f6() {
    /*...*/
    callAFunction("parameter6");
}
function f7() {
    /*...*/
    callAFunction("parameter7");
}
function f8() {
    /*...*/
    callAFunction("parameter8");
}
function f9() {
    /*...*/
    callAFunction("parameter9");
}
    
function callAllFunctionsSy(params) {
        functionsToCall.Push(f1);
        functionsToCall.Push(f2);
        functionsToCall.Push(f3);
        functionsToCall.Push(f4);
        functionsToCall.Push(f5);
        functionsToCall.Push(f6);
        functionsToCall.Push(f7);
        functionsToCall.Push(f8);
        functionsToCall.Push(f9);
        functionsToCall.reverse();
        callAFunction(params);
}

function callAFunction(params) {
        if (functionsToCall.length > 0) {
                var f=functionsToCall.pop();
                f(params);
        }
}
1

コードを文字列に入れ、反復、評価、setTimeout、および再帰を実行して、残りの行を続行します。間違いなくこれを改良するか、それがマークに当たらない場合は単に捨てます。私の意図は、これを使用して、本当に基本的なユーザーテストをシミュレートすることです。

再帰とsetTimeoutにより、順次になります。

考え?

var line_pos = 0;
var string =`
    console.log('123');
    console.log('line pos is '+ line_pos);
SLEEP
    console.log('waited');
    console.log('line pos is '+ line_pos);
SLEEP
SLEEP
    console.log('Did i finish?');
`;

var lines = string.split("\n");
var r = function(line_pos){
    for (i = p; i < lines.length; i++) { 
        if(lines[i] == 'SLEEP'){
            setTimeout(function(){r(line_pos+1)},1500);
            return;
        }
        eval (lines[line_pos]);
    }
    console.log('COMPLETED READING LINES');
    return;
}
console.log('STARTED READING LINES');
r.call(this,line_pos);

出力

STARTED READING LINES
123
124
1 p is 0
undefined
waited
p is 5
125
Did i finish?
COMPLETED READING LINES
0
Jamie Nicholson

これを見る別の方法は、ある機能から別の機能にデイジーチェーン接続することです。呼び出されたすべての関数に対してグローバルな関数の配列を持っている、と言う:

arrf: [ f_final
       ,f
       ,another_f
       ,f_again ],

次に、実行したい特定の「f」に整数の配列を設定します、例えば

var runorder = [1,3,2,0];

次に、「runorder」をパラメーターとして使用して初期関数を呼び出します。 f_start(runorder);

次に、各関数の最後で、次の「f」へのインデックスをポップしてrunorder配列から実行し、実行します。ただし、「runorder」をパラメーターとして渡しますが、配列は1つ減らされます。

var nextf = runorder.shift();
arrf[nextf].call(runorder);

明らかに、これは別の関数に連鎖しない関数、たとえばインデックス0で終了します。これは完全に決定的であり、「タイマー」を回避します。

0
Allan