明示的なプロミスコンストラクションアンチパターンとは何ですか?


516

私は次のようなことをするコードを書いていました:

function getStuffDone(param) {           | function getStuffDone(param) {
    var d = Q.defer(); /* or $q.defer */ |     return new Promise(function(resolve, reject) {
    // or = new $.Deferred() etc.        |     // using a promise constructor
    myPromiseFn(param+1)                 |         myPromiseFn(param+1)
    .then(function(val) { /* or .done */ |         .then(function(val) {
        d.resolve(val);                  |             resolve(val);
    }).catch(function(err) { /* .fail */ |         }).catch(function(err) {
        d.reject(err);                   |             reject(err);
    });                                  |         });
    return d.promise; /* or promise() */ |     });
}                                        | }

これはそれぞれ「遅延アンチパターン」または「Promiseコンストラクタアンチパターン」と呼ばれていると誰かから言われました。このコードの何が悪いのですか、なぜこれがアンチパターンと呼ばれているのですか?


これをgetStuffDone理解するために、関数ラッパーを(左ではなく、右のコンテキストで)削除して、Promiseリテラルだけを使用することを確認できますか?
デンビンスキー2017年

1
またはラッパーにcatchブロックがあるのgetStuffDoneはアンチパターンですか?
デンビンスキー2017年

1
少なくともネイティブのPromise例では.then.catchハンドラーとハンドラーに不要な関数ラッパーも用意されています(つまり、それだけの可能性があり.then(resolve).catch(reject)ます)。アンチパターンの完全な嵐。
Noah Freitas 2017

6
@NoahFreitasそのコードは教訓的な目的のためにそのように書かれています。そのようなコードをたくさん読んだ後にこの問題に
遭遇した

明示的なPromiseの構築だけでなく、グローバル変数の使用も排除する方法については、stackoverflow.com / questions / 57661537 /…も参照してください。
David Spector

回答:


357

繰延アンチパターン(今明示建設アンチパターン)によって造語Esailijaが、私は最初の約束を使用したときに約束を作るために新しく追加された一般的なアンチパターンの人ですが、私はそれを自分で作りました。上記のコードの問題は、チェーンを約束するという事実を利用できないことです。

プロミスは連鎖.thenでき、プロミスを直接返すことができます。のコードgetStuffDoneは次のように書き直すことができます。

function getStuffDone(param){
    return myPromiseFn(param+1); // much nicer, right?
}

約束はすべて、非同期コードを読みやすくし、その事実を隠すことなく同期コードのように動作させることです。Promiseは、1回限りの操作の値に対する抽象化を表し、プログラミング言語のステートメントまたは式の概念を抽象化します。

APIをpromiseに変換して自動的に実行できない場合、またはこの方法で簡単に表現できる集計関数を作成している場合にのみ、遅延オブジェクトを使用する必要があります

エサイリヤの引用:

これは最も一般的なアンチパターンです。約束を本当に理解しておらず、約束を美化されたイベントエミッターまたはコールバックユーティリティと見なすと、これに陥りやすくなります。要約すると、約束は、非同期コードに、フラットインデントや1つの例外チャネルなど、同期コードの失われたプロパティのほとんどを保持させることです。


@BenjaminGruenbaum:これに遅延オブジェクトを使用することに自信があるので、新しい質問は必要ありません。私はそれがあなたの答えに欠けていたユースケースだと思っただけです。私がやっていることは、集約の反対のように思えますね。
mhelvens 2014

1
@mhelvens非コールバックAPIを手動で「コールバックAPIをpromiseに変換する」部分に適合するpromise APIに分割する場合。アンチパターンとは、プロミスを別のプロミスにラップすることです。正当な理由はありません。プロミスを最初からラップするわけではないため、ここでは適用されません。
Benjamin Gruenbaum 2014

@BenjaminGruenbaum:ああ、私は据え置き自体はアンチパターンと見なされていましたが、bluebirdが非推奨にしていて、「APIをpromiseに変換する」(これは最初からpromiseをラップしない場合でもあります)と述べています。
mhelvens 2014

@mhelvens 延期された過剰なアンチパターンは、実際の動作に対してより正確になると思います。Bluebirdは.defer()APIを新しい(そして安全なスロー)プロミスコンストラクターに非推奨にしましたが、プロミスの構築という概念を(決して)非推奨しませんでした:)
Benjamin Gruenbaum '25

1
ありがとう@ Roamer-1888あなたの参照は私が最終的に私の問題が何であるかを理解するのに役立ちました。ネストされた(返されない)プロミスを実現せずに作成していたようです。
ghuroo 2017年

134

どうしたの?

しかし、パターンは機能します!

あなたはラッキーです。残念ながら、いくつかのエッジケースを忘れている可能性があるため、おそらくそうではありません。私が見た発生の半分以上で、作者はエラーハンドラの世話を忘れていました:

return new Promise(function(resolve) {
    getOtherPromise().then(function(result) {
        resolve(result.property.example);
    });
})

他のプロミスが拒否された場合、これは新しいプロミス(プロミスが処理される場所)に伝播される代わりに気付かれずに発生し、新しいプロミスは永久に保留されたままになるため、リークが発生する可能性があります。

コールバックコードでエラーが発生した場合も同じことが起こります。たとえば、resultがなくproperty、例外がスローされた場合です。それは処理されず、新しい約束は未解決のままになります。

対照的に、を使用.then()すると、これらのシナリオの両方が自動的に処理され、エラーが発生すると新しいプロミスが拒否されます。

 return getOtherPromise().then(function(result) {
     return result.property.example;
 })

延期されたアンチパターンは扱いにくいだけでなく、エラーが発生しやすくなります。.then()チェーンに使用する方がはるかに安全です。

しかし、すべてを処理しました!

本当に?良い。ただし、特にキャンセルやメッセージパッシングなどの他の機能をサポートするpromiseライブラリを使用する場合は、これはかなり詳細で豊富です。それとも将来的にはそうなるでしょうか、それともライブラリをより良いライブラリと交換したいですか?そのためにコードを書き直す必要はありません。

ライブラリのメソッド(then)は、すべての機能をネイティブでサポートするだけでなく、特定の最適化を行っている場合もあります。それらを使用すると、コードがより高速になるか、少なくともライブラリの将来のリビジョンで最適化できるようになります。

どうすれば回避できますか?

したがって、手動で、PromiseまたはDeferred既存のプロミスを作成していることに気づいた場合は、最初にライブラリAPIを確認してください。繰延アンチパターンは、多くの場合、[のみ] Observerパターンとして約束を見る人によって適用される-しかし、約束がある以上、コールバックより:それらが構成可能なことになっています。すべてのまともなライブラリには、考えたくない方法でプロミスを構成するための使いやすい関数がたくさんあり、処理したくないすべての低レベルのものに対応しています。

既存のヘルパー関数ではサポートされていない新しい方法でいくつかのpromiseを作成する必要がある場合は、避けられないDeferredを使用して独自の関数を作成するのが最後のオプションです。より機能的なライブラリへの切り替えを検討するか、現在のライブラリに対してバグを報告してください。そのメンテナは、既存の関数から構成を導き出し、新しいヘルパー関数を実装し、処理する必要があるエッジケースを特定するのに役立つ必要があります。


setTimeout使用できるが、「Promiseコンストラクターanitpattern」と見なされない関数を含む例はありますか?
guest271314 2015年

1
@ guest271314:プロミスを返さない非同期のすべて。多くの場合、ライブラリの専用のPromisificationヘルパーでより良い結果を得ることができます。そして、常に最低レベルで約束するようにしてください。これは、「を含む関数setTimeout」ではなく、「関数setTimeout自体」です。
Bergi、2015年

「そして、常に最低レベルで約束するようにしてください。それは、「を含む機能setTimeout」ではなく、「機能setTimeout自体」ではありませんか?
guest271314 2015年

@ guest271314への呼び出しだけを含む関数setTimeout、関数setTimeout自体とは明らかに異なりますね。
Bergi、2015年

4
ここで重要な教訓の1つは、これまで明確に述べられていなかったことですが、Promiseとそのチェーン「then」は1つの非同期操作を表すということです。最初の操作はPromiseコンストラクターにあり、最終的なエンドポイントは「 then '関数。したがって、同期操作の後に非同期操作がある場合は、同期のものをPromiseに配置します。非同期操作の後に同期がある場合は、同期の要素を「その後」に配置します。最初のケースでは、元のPromiseを返します。2番目のケースでは、Promise / thenチェーン(これもPromiseです)を返します。
デビッドスペクター
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.