web-dev-qa-db-ja.com

明示的な約束の構築の逆パターンとは何ですか?また、どうすればそれを回避できますか?

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

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コンストラクタantipattern"と呼ばれています。なぜこれが アンチパターン と呼ばれるのですか。

445

延期対パターン(現在は明示的構造対パターン) によって作られた Esailija は、約束が不慣れな一般的な対パターンの人々です。私が最初に約束を使ったときにそれを自分で作った。上記のコードの問題点は、連鎖を約束するという事実を利用できないことです。

約束は.thenと連鎖することができ、あなたは直接約束を返すことができます。 getStuffDone内のコードは次のように書き直すことができます。

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

約束は、非同期コードを読みやすくし、その事実を隠さずに同期コードのように振る舞わせることにあります。約束は、一回限りの操作の価値を抽象化したものであり、プログラミング言語におけるステートメントまたは式の概念を抽象化したものです。

APIをpromise に変換していて自動的に実行できない場合、またはこのように表現しやすい集約関数を作成している場合にのみ、遅延オブジェクトを使用してください。

Esailijaを引用:

これが最も一般的なアンチパターンです。あなたが本当に約束を理解しておらず、それらを賛美されたイベント発行者またはコールバックユーティリティとして考えるとき、これに陥るのは簡単です。要約しましょう。約束は、フラット・インデントや1つの例外チャネルなど、同期コードの失われた特性の大部分を非同期コードに保持させることです。

309

どうしたんだ?

しかし、パターンはうまくいきます!

あなたはラッキーです。残念ながら、おそらくそうではありません。おそらくEdgeのケースを忘れていたでしょう。私がこれまでに経験したことの半分以上で、作者はエラーハンドラの面倒を見るのを忘れていました:

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

他の約束が却下された場合、これは新しい約束(それが処理される場所)に伝播されるのではなく気付かれずに起こります - そして新しい約束は永遠に保留のままになり、リークを引き起こす可能性があります。

あなたのコールバックコードがエラーを起こした場合にも同じことが起こります。 resultpropertyを持たず、例外が投げられたとき。それは処理されずに進み、新しい約束は未解決のままになります。

対照的に、.then()を使うことは自動的にこれら両方のシナリオを引き受けて、そしてエラーが起こるとき新しい約束を拒絶します:

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

延期されたアンチパターンは、面倒なだけでなく、エラーが発生しやすいでもあります。連鎖のために.then()を使うほうがはるかに安全です。

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

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

ライブラリのメソッド(then)は、すべての機能をネイティブにサポートするだけでなく、特定の最適化が施されている場合もあります。それらを使用することはあなたのコードをより速くするか、少なくとも将来のライブラリの改訂によって最適化されることを可能にするでしょう。

どうやってそれを避けるのですか?

そのため、手動でPromiseまたはDeferredを作成していて、既存の約束が含まれている場合は、必ずまずライブラリAPIをチェックしてください。 Deferred antipatternは、promiseをオブザーバパターンとして見ているだけの人によく適用されます - しかし、 promiseはmoreよりコールバック) :彼らは構成できると思われます。あなたが対処したくないすべての低レベルのものの世話をしながら、あらゆる考えられる方法で約束を構成するための使いやすい関数。

既存のヘルパー関数ではサポートされていない、新しい方法でいくつかの約束を作成する必要があることがわかった場合は、避けられないDeferredを使って独自の関数を書くことが最後の選択肢です。より機能的なライブラリに切り替えることを検討するか、現在のライブラリに対してバグを報告してください。そのメンテナは、既存の関数から構成を導き出し、あなたのために新しいヘルパー関数を実装し、そして/または扱う必要があるEdgeケースを識別するのを助けることができるはずです。

118
Bergi