続行する前に1つの関数が完了するのを待つ適切な方法は?


187

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で?


11
firstFunctionそれは正確にそれを非同期にしますか?どちらにしても、約束について確認する
zerkms

最初の関数は、10秒ごとにスコアクロックを急速に更新しています。考えてみると、事前計算してから、setTimeoutを介して2番目の関数呼び出しを手動で一時停止できると思います。そうは言っても、他の場所で一時停止能力を持ちたいという欲求はまだあります。
DA。

あなたは一時停止の必要はありません
-jqueryのpromiseのグーグル

@zerkms promiseは面白そうです!引き続きブラウザのサポートを調査しています...
DA。

1
私も同じ問題を抱えています。
Vapor Washmade

回答:


141

このような非同期作業に対処する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!'))


更新:私はかなり前にこれに答えました、そして本当にそれを更新したいです。コールバックは絶対に問題ありませんが、私の経験では、コードの読み取りと維持がより困難になる傾向があります。ただし、進行中のイベントなどをパラメーターとして渡す場合など、まだ使用している状況があります。このアップデートは、選択肢を強調するためのものです。

また、元の質問では特に非同期について言及していません。そのため、誰かが混乱した場合、関数が同期している、呼び出されたときブロックされます。例えば:

doSomething()
// the function below will wait until doSomething completes if it is synchronous
doSomethingElse()

暗黙的に関数が非同期であるとしても、今日のすべての非同期作業を処理する方法は、非同期/待機です。例えば:

const secondFunction = async () => {
  const result = await firstFunction()
  // do something else here after firstFunction completes
}

IMO、async / awaitは、ほとんどの場合、promiseを直接使用するよりもコードを読みやすくします。キャッチエラーを処理する必要がある場合は、try / catchで使用します。詳しくは、https//developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_functionをご覧ください


9
@zerkms-詳しく説明しますか?
Matt Way、

7
コールバックは非同期を処理するのに不便です。約束ははるかに簡単で柔軟です
zerkms

1
私が最高の(更新された)という言葉を使うべきではなかったのはあなたの言うとおりですが、コールバックとプロミスの利便性は、問題の複雑さに依存します。
Matt Way、

3
それはトピック外になりますが、私は最近コールバックを好む理由を個人的には見ていません:-)過去2年間に書かれた私のコードは約束よりもコールバックを提供することを思い出せません。
zerkms 2014

3
必要に応じて、es6でアロー関数を使用できます。secondFunction(){firstFunction((response)=> {console.log(response);}); }
Janaka Pushpakumara 2017

53

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
};

9
古いブラウザのサポートが行き詰まっている私たちにとって、IEは非同期/
待機を

これは、両方の関数の外で使用できます$(function() { await firstFunction(); secondFunction(); });か?
Lewistrick

1
@Lewistrick Just remember awaitasyncメソッド内でのみ使用できます。したがって、親関数を作成する場合、必要にasync応じてasync methods内部または内部で呼び出すことができますawait
Fawaz

それはすることが可能になりますか?awaitsetTimeout
シャヤン

1
@Shayanはい、そうです。タイムアウト後にプロミスを解決する別の関数でsetTimeoutをラップします。
Fawaz

51

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。「こちら」のアラートが表示isPausedされたら、falseすでに表示され、firstFunction戻ってきます。これは、forループ(// do something)の内側から「譲る」ことができないため、ループが中断されず、最初に完全に完了する必要があるためです(詳細:JavaScriptのスレッド処理と競合状態)。

それでも、内部のコードフローfirstFunctionを非同期にして、コールバックまたはpromiseを使用して呼び出し元に通知することができます。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に限定されています。


3
あなたは私を救いました。$ .Deferred()は私が狙っていたものです。ありがとう
Temitayo 2017

@noseratio私はこれを試しました。しかし、waitForItメソッドはまったく呼び出されません。何が悪いのですか?
vigamage

@vigamage、あなたが試したことのjsfiddleまたはcodepenへのリンクを提供できますか?
noseratio

1
こんにちは、ご心配ありがとうございます。うまくいきました。ありがとうございました..!
vigamage

18

最初に完了するために一つの機能が使用するためにエレガントな方法が待機する約束をして非同期/のawait機能。


  1. まず、Promiseを作成します。作成した関数は2秒後に完成します。setTimeout命令の実行に時間がかかる状況を示すために使用し ました。
  2. 2番目の関数の場合は、async / await 関数を使用awaitして、最初の関数が完了してから手順を進めることができます 。

例:

    //1. Create a new function that returns a promise
    function firstFunction() {
      return new Promise((resolve, reject) => {
          let y = 0
          setTimeout(() => {
            for(i=0; i<10; i++){
               y++
            }
             console.log('loop completed')  
             resolve(y)
          }, 2000)
      })
    }
    
    //2. Create an async function
    async function secondFunction() {
        console.log('before promise call')
        //3. Await for the first function to complete
        let result = await firstFunction()
        console.log('promise resolved: ' + result)
        console.log('next step')
    }; 

    secondFunction()


注意:

あなたは単にresolveそのPromiseような値なしで単にできresolve()ます。私の例では、I の値を持つIが第2の機能に使用することができます。resolvedPromisey


はい、これは常に機能します。この種のシナリオに関与している人がいる場合、JavaScript Promiseがそのトリックを実行します。
Puttamarigowda MS

5

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

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

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

ただし、次の状況のように、非同期呼び出しをスローします。

showLoadingDiv(); // function 1

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

hideLoadingDiv(); // function 3

これはあなたが望むことをしません。関数1、関数2、関数3を瞬時に実行します。divのロードがフラッシュして消えますが、ajaxの呼び出しは、makeAjaxCall()戻ってきてもほぼ完了していません。複雑さは、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

また、この主題に関する同様の非常に評価の高い別の質問:3つの関数を順番に実行するには、どのように呼び出す必要がありますか?


8
反対投票者には、この回答の何が問題なのか知りたいです。
カーミット

0

チェーンでいくつかの操作を実行する必要があるので、これは私が思いついたものです。

<button onclick="tprom('Hello Niclas')">test promise</button>

<script>
    function tprom(mess) {
        console.clear();

        var promise = new Promise(function (resolve, reject) {
            setTimeout(function () {
                resolve(mess);
            }, 2000);
        });

        var promise2 = new Promise(async function (resolve, reject) {
            await promise;
            setTimeout(function () {
                resolve(mess + ' ' + mess);
            }, 2000);
        });

        var promise3 = new Promise(async function (resolve, reject) {
            await promise2;
            setTimeout(function () {
                resolve(mess + ' ' + mess+ ' ' + mess);
            }, 2000);
        });

        promise.then(function (data) {
            console.log(data);
        });

        promise2.then(function (data) {
            console.log(data);
        });

        promise3.then(function (data) {
            console.log(data);
        });
    }

</script>

指定されたスニペットの説明については、少なくとも高レベルのコメントを追加してください。これは、コードが問題にどのように対処するかを説明するためのものです。
コールドケルベロス

問題は、最初の関数の後に2番目の関数を実行することでした。最初の関数の後に2番目の関数を実行する方法と、2番目の関数の後に3番目の関数を実行する方法も示します。必要なコードを簡単に理解できるように、3つの関数を作成しました。
Niclas Arnberger

0

あなたの主な楽しみはfirstFunを呼び出し、それが完了すると次の楽しみが呼び出されます。

async firstFunction() {
            const promise = new Promise((resolve, reject) => {
                for (let i = 0; i < 5; i++) {
                    // do something
                    console.log(i);
                    if (i == 4) {
                        resolve(i);
                    }
                }
            });
            const result = await promise;
        }

        second() {
            this.firstFunction().then( res => {
                // third function call do something
                console.log('Gajender here');
            });
        }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.