web-dev-qa-db-ja.com

続行する前に1つの機能が終了するのを待つための正しい方法は?

私は2つのJS関数を持っています。一方が他方を呼び出す。呼び出し元の関数内で、もう一方を呼び出し、その関数が終了するのを待ってから続行します。だから、例えば/擬似コード:

function firstFunction(){
    for(i=0;i<x;i++){
        // do something
    }
};

function secondFunction(){
    firstFunction()
    // now wait for firstFunction to finish...
    // do something else
};

私はこの解決策を思い付きましたが、これがそれについてうまくいくかどうかわからない。

var isPaused = false;

function firstFunction(){
    isPaused = true;
    for(i=0;i<x;i++){
        // do something
    }
    isPaused = false;
};

function secondFunction(){
    firstFunction()
    function waitForIt(){
        if (isPaused) {
            setTimeout(function(){waitForIt()},100);
        } else {
            // go do that thing
        };
    }
};

それは合法ですか?それを処理するためのよりエレガントな方法はありますか?おそらくjQueryと?

122
DA.

このような非同期の作業を扱う1つの方法は、コールバック関数を使うことです。例えば:

function firstFunction(_callback){
    // do some asynchronous work
    // and when the asynchronous stuff is complete
    _callback();    
}

function secondFunction(){
    // call first function and pass in a callback function which
    // first function runs when it has completed
    firstFunction(function() {
        console.log('huzzah, I\'m done!');
    });    
}

@Janaka Pushpakumaraの提案どおり、矢印機能を使用して同じことを実行できます。例えば:

firstFunction(() => console.log('huzzah, I\'m done!'))

106
Matt Way

ここに重要なポイントが欠けているようです。JavaScriptはシングルスレッドの実行環境です。 alert("Here")を追加したことに注意してください。

var isPaused = false;

function firstFunction(){
    isPaused = true;
    for(i=0;i<x;i++){
        // do something
    }
    isPaused = false;
};

function secondFunction(){
    firstFunction()

    alert("Here");

    function waitForIt(){
        if (isPaused) {
            setTimeout(function(){waitForIt()},100);
        } else {
            // go do that thing
        };
    }
};

isPausedを待つ必要はありません。 "Here"アラートが表示されたら、isPausedはすでにfalseになり、firstFunctionが返されます。これは、forループ(// do something)の内部から「譲る」ことができないため、ループが中断されず、最初に完全に完了する必要があるためです(詳細は Javascriptのスレッド処理と競合条件 ).

それでも、firstFunction内のコードフローを非同期にして、コールバックまたはプロミスを使用して呼び出し元に通知することができます。 forループをあきらめて、代わりにifでシミュレートする必要があります( JSFiddle )。

function firstFunction()
{
    var deferred = $.Deferred();

    var i = 0;
    var NeXTSTEP = function() {
        if (i<10) {
            // Do something
            printOutput("Step: " + i);
            i++;
            setTimeout(NeXTSTEP, 500); 
        }
        else {
            deferred.resolve(i);
        }
    }
    NeXTSTEP();
    return deferred.promise();
}

function secondFunction()
{
    var promise = firstFunction();
    promise.then(function(result) { 
        printOutput("Result: " + result);
    });
}

ちなみに、JavaScript 1.7は ジェネレータ の一部としてyieldキーワードを導入しました。そうでなければ同期的なJavaScriptコードフローに非同期の穴を「パンチ」できるようになります( 詳細と例 )。しかし、ジェネレータのブラウザサポートは現在FirefoxとChrome、AFAIKに限定されています。

45
noseratio

Async/awaitを使う:

async function firstFunction(){
  for(i=0;i<x;i++){
    // do something
  }
  return;
};

それからそれが戻るのを待つためにあなたの他の関数でawaitを使ってください:

async function secondFunction(){
  await firstFunction();
  // now wait for firstFunction to finish...
  // do something else
};
30
Fawaz

Promiseの唯一の問題はIEがそれらをサポートしていないことです。 Edgeにはありますが、たくさんのIE 10と11があります: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/約束 (一番下の互換性)

そのため、JavaScriptはシングルスレッドです。非同期呼び出しをしていない場合は、予測どおりに動作します。メインのJavaScriptスレッドは、コードに表示されている順序で、次の関数を実行する前に1つの関数を完全に実行します。同期関数の順序を保証するのは簡単です - 各関数は呼び出された順序で完全に実行されます。

同期関数をアトミックな作業単位と見なします。メインのJavaScriptスレッドは、ステートメントがコードに表示されている順序で完全に実行します。

しかし、次のように非同期呼び出しを投入します。

showLoadingDiv(); // function 1

makeAjaxCall(); // function 2 - contains async ajax call

hideLoadingDiv(); // function 3

これはあなたが望むことをしません。関数1、関数2、および関数3を瞬時に実行します。divのロードが点滅して終了しましたが、makeAjaxCall()が戻ってもajax呼び出しはほぼ完了していません。 複雑さは、makeAjaxCall()がそれぞれのスピンによって少しずつ進められるチャンクに作業を分割したということです。メインJavaScriptスレッド - 非同期的に動作しています。しかし、同じメインスレッドは、1回のスピン/実行中に同期部分を迅速かつ予測可能に実行しました。

それで、私がそれを扱った方法:私が言ったように、関数はアトミックな作業単位です。私は機能1と2のコードを組み合わせました - 非同期呼び出しの前に、機能1のコードを機能2に入れました。私は機能1を取り除きました。非同期呼び出しまで(非同期呼び出しを含む)はすべて順不同で予測通りに実行されます。

その場合、メインのJavaScriptスレッドを数回スピンした後に非同期呼び出しが完了したときに、関数3を呼び出すようにします。これにより順序が保証されます。たとえば、ajaxでは、onreadystatechangeイベントハンドラは複数回呼び出されます。完了したことが報告されたら、必要な最後の関数を呼び出します。

私はそれが面倒だと思う。コードを対称にしたり、関数に1つのこと(またはそれに近いこと)をさせたりすることが好きです。また、ajax呼び出しが表示を担当すること(呼び出し元への依存関係を作成すること)は好きではありません。しかし、同期関数に非同期呼び出しが組み込まれている場合は、実行順序を保証するために妥協が必要です。そして私はIE 10をコーディングしなければならないので約束はありません。

まとめ:同期呼び出しの場合、順序の保証は簡単です。各関数は、呼び出された順序で完全に実行されます。非同期呼び出しを伴う関数の場合、順序を保証する唯一の方法は、非同期呼び出しがいつ完了したかをモニターし、その状態が検出されたときに3番目の関数を呼び出すことです。

JavaScriptスレッドについては、 https://medium.com/@francesco_rizzi/javascript-main-thread-dissected-43c85fce7e23 および https:/を参照してください。 /developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop

また、この問題に関するもう1つの同様の高い評価の質問: それらを次々と実行するために3つの関数を呼び出すにはどうすればいいですか?

3
kermit

1つの関数が最初に完了するのを待つためのエレガントな方法は、 Promiseasync/await 関数を使用することです。

簡単な例:

let firstFunction = new Promise(function(resolve, reject) {
    let x = 10
    let y = 0
    setTimeout(function(){
      for(i=0;i<x;i++){
         y++
      }
       console.log('loop completed')  
       resolve(y)
    }, 2000)
})

まず、 Promise を作成してください。上記の機能は2秒後に完了します。 (あなたの例に基づいて)命令が実行に時間がかかる状況を示すためにsetTimeoutを使用しました。

async function secondFunction(){
    let result = await firstFunction
    console.log(result)
    console.log('next step')
}; 

secondFunction()

2番目の関数では、 async/await 関数を使用できます。最初の関数が完了するまでawaitで指示を続けることができます。

注:resolve()のように値を指定せずに単純にPromiseを解決できます。私の例では、2番目の関数で使用できるyの値でPromiseを解決しました。

まだこのスレッドにアクセスしている人に役立つことを願います。

3