JavaScript Promiseが解決するのを待ってから関数を再開するにはどうすればよいですか?


107

単体テストを行っています。テストフレームワークは、ページをiFrameに読み込み、そのページに対してアサーションを実行します。各テストを開始する前にPromise、iFrameのonloadイベントを呼び出すようresolve()に設定し、iFrameを設定しsrc、promiseを返すを作成します。

したがって、を呼び出すだけloadUrl(url).then(myFunc)で、ページがロードされるのを待ってから何を実行することもできmyFuncます。

この種のパターンは、主にDOMへの変更を許可するために(URLのロードだけでなく)、テストのあちこちで使用されています(たとえば、ボタンのクリックを模倣し、divが非表示になって表示されるのを待ちます)。

この設計の欠点は、数行のコードを含む匿名関数を常に記述していることです。さらに、回避策(QUnit's assert.async())がありますが、プロミスを定義するテスト関数は、プロミスが実行される前に完了します。

から値を取得する方法、Promiseまたは.NETのように解決されるまで待機(ブロック/スリープ)する方法があるかどうか疑問に思っていますIAsyncResult.WaitHandle.WaitOne()。私はJavaScriptがシングルスレッドであることを知っていますが、それが関数が生成できないことを意味しないことを願っています。

本質的に、正しい順序で結果を吐き出すために以下を取得する方法はありますか?

function kickOff() {
  return new Promise(function(resolve, reject) {
    $("#output").append("start");
    
    setTimeout(function() {
      resolve();
    }, 1000);
  }).then(function() {
    $("#output").append(" middle");
    return " end";
  });
};

function getResultFrom(promise) {
  // todo
  return " end";
}

var promise = kickOff();
var result = getResultFrom(promise);
$("#output").append(result);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"></div>


追加呼び出しを再利用可能な関数に入れる場合は、必要に応じてDRYすることができます。また、多目的ハンドラーを作成し、これによってのようなthen()呼び出しをフィード.then(fnAppend.bind(myDiv))して、匿名を大幅に削減できます。
dandavis 2015年

何をテストしていますか?それが最新のブラウザーであるか、BabelJSのようなツールを使用してコードをトランスパイルできる場合、これは確かに可能です。
Benjamin Gruenbaum 2015年

回答:


78

.NETのIAsyncResult.WaitHandle.WaitOne()のように、Promiseから値を取得するか、それが解決されるまで待機(ブロック/スリープ)する方法があるかどうか疑問に思っています。私はJavaScriptがシングルスレッドであることを知っていますが、それが関数が生成できないことを意味しないことを願っています。

現在の世代のブラウザのJavascriptには、wait()やがないため、sleep()他のものを実行できます。だから、あなたが求めていることを単に行うことはできません。代わりに、それはそれらのことを行い、それらが完了したときに(promiseを使用してきたように)呼び出す非同期操作を持っています。

これの一部は、Javascriptのシングルスレッド化によるものです。単一のスレッドが回転している場合、その回転するスレッドが完了するまで、他のJavaScriptは実行できません。ES6はyield、そのようないくつかの協調的なトリックを可能にするジェネレーターを導入しますが、インストールされたブラウザーの広い見本でそれらを使用できるようになることはかなりの方法です(これらは、JSエンジンを制御するサーバー側の開発で使用できます)それが使用されています)。


promiseベースのコードを注意深く管理すると、多くの非同期操作の実行順序を制御できます。

コードで達成しようとしている順序を正確に理解しているとは思いませんが、既存のkickOff()関数を使用して次のようなことを行い.then()、それを呼び出した後にハンドラーをアタッチすることができます。

function kickOff() {
  return new Promise(function(resolve, reject) {
    $("#output").append("start");

    setTimeout(function() {
      resolve();
    }, 1000);
  }).then(function() {
    $("#output").append(" middle");
    return " end";
  });
}

kickOff().then(function(result) {
    // use the result here
    $("#output").append(result);
});

これは保証された順序で出力を返します-このように:

start
middle
end

2018年の更新(この回答が書かれてから3年後):

あなたはどちらかあなたのコードをtranspileまたはサポートES7のような特徴という環境の中で、あなたのコードを実行した場合asyncawait、あなたが今使用することができawait、あなたのコードが「見える」にするために約束の結果を待ちます。それはまだ約束で発展しています。それでもすべてのJavaScriptがブロックされるわけではありませんが、よりわかりやすい構文で順次操作を記述できます。

ES6の方法の代わりに:

someFunc().then(someFunc2).then(result => {
    // process result here
}).catch(err => {
    // process error here
});

あなたはこれを行うことができます:

// returns a promise
async function wrapperFunc() {
    try {
        let r1 = await someFunc();
        let r2 = await someFunc2(r1);
        // now process r2
        return someValue;     // this will be the resolved value of the returned promise
    } catch(e) {
        console.log(e);
        throw e;      // let caller know the promise was rejected with this reason
    }
}

wrapperFunc().then(result => {
    // got final result
}).catch(err => {
    // got error
});

3
はい、使用することthen()は私がやっていることです。いつも書いfunction() { ... }ている必要はありません。コードが雑然としている。
dx_over_dt 2015年

3
@dfoverdx-JavaScriptの非同期コーディングには常にコールバックが含まれるため、常に関数(匿名または名前付き)を定義する必要があります。現在それを回避する方法はありません。
jfriend00 2015年

2
ES6の矢印表記を使用すると、より簡潔にすることができます。(foo) => { ... }代わりにfunction(foo) { ... }
Rob H

1
@RobHコメントに頻繁に追加してfoo => single.expression(here)、中括弧と丸括弧を取り除くように書くこともできます。
begie

3
@dfoverdx-私の回答から3年後、私はそれをES7 asyncawait構文で更新し、オプションとしてnode.jsと最新のブラウザーまたはトランスパイラーを介して使用できるようになりました。
jfriend00

15

ES2016を使用している場合、あなたは使用することができるasyncawaitしてのようなものを実行します。

(async () => {
  const data = await fetch(url)
  myFunc(data)
}())

ES2015を使用している場合は、ジェネレータを使用できます。あなたは構文気に入らない場合、あなたはそれが離れて抽象使用できるasyncユーティリティ関数をとして、ここで説明しました

ES5を使用している場合は、Bluebirdのようなライブラリを使用して、より細かく制御できるようにする必要があります。

最後に、ランタイムがES2015をサポートしている場合、実行順序はFetch Injectionを使用して並列処理で保持される可能性があります。


1
あなたのジェネレーターの例は動作しません、それはランナーが欠けています。ES8 async/をトランスパイルできるだけの場合は、ジェネレータを推奨しないでくださいawait
Bergi

3
ご意見をいただきありがとうございます。Generatorの例を削除し、代わりに、関連するOSSライブラリを含むより包括的なGeneratorチュートリアルにリンクしました。
Josh Habdas

9
これは単に約束を包むだけです。async関数は、実行時に何も待機せず、promiseを返します。async/ awaitは、のもう1つの構文ですPromise.then
rustyx

あなたは絶対的に正しいしているasync待つことはありません...それはない限り、生成使用しますawait。ES2016 / ES7の例はまだ実行できないので、3年前にこの動作を示すために書いたコードをいくつか示します。
Josh Habdas

7

別のオプションは、Promise.allを使用して、約束の配列が解決されるのを待つことです。以下のコードは、すべての約束が解決するのを待って、すべての準備ができたら結果を処理する方法を示しています(これが質問の目的のように思われました)。ただし、resolveを呼び出す前にそのコードを追加するだけで、middleが解決する前にstartが出力を出力することは明らかです。

function kickOff() {
  let start = new Promise((resolve, reject) => resolve("start"))
  let middle = new Promise((resolve, reject) => {
    setTimeout(() => resolve(" middle"), 1000)
  })
  let end = new Promise((resolve, reject) => resolve(" end"))

  Promise.all([start, middle, end]).then(results => {
    results.forEach(result => $("#output").append(result))
  })
}

kickOff()
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"></div>


1

手動で行うことができます。(私は知っています、それは素晴らしい解決策ではありませんが..)値がなくなるwhileまでループを使用resultします

kickOff().then(function(result) {
   while(true){
       if (result === undefined) continue;
       else {
            $("#output").append(result);
            return;
       }
   }
});

これは待機中にCPU全体を消費します。
Lukasz Guminski

これは恐ろしいソリューションです
JSON
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.