明示的なプロミス構築のアンチパターンとは何ですか?それを回避するにはどうすればよいですか?

May 22 2014

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

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コンストラクターアンチパターン」と呼んでいると言いましたが、このコードの何が悪いのか、なぜこれがアンチパターンと呼ばれるのですか?

回答

380 BenjaminGruenbaum May 22 2014 at 17:07

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

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

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

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

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

Esailijaの引用:

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

142 Bergi Aug 29 2014 at 20:28

どうしたんだ?

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

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

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

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

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

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

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

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

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

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

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

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

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

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