明示的な約束構造のアンチパターンとは何か、それを回避するにはどうすればいいのか

javascript promise q bluebird es6-promise


みたいなことをするコードを書いていました。

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




Answer 1 Benjamin Gruenbaum


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

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

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

プロミスは、非同期コードをより読みやすくし、その事実を隠すことなく同期コードのように振る舞うことができるようにするためのものです。プロミスは1回の操作の値を抽象化したもので、プログラミング言語の文や式の概念を抽象化したものです。

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

エサイリヤを引用して

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




Answer 2 Bergi


何が悪いの?

でも、パターンが効いている!

運がいいわね 残念ながら、おそらくエッジケースを忘れているのでしょう。私が見てきた事例の半分以上では、作者はエラーハンドラの処理を忘れていました。

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() を使用すると、はるかに安全です。

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

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

ライブラリのメソッド( then 唯一のネイティブすべての機能をサポートしていませんが)、彼らはまた、所定の位置に特定の最適化を持っているかもしれません。それらを使用すると、コードがより高速になるか、少なくともライブラリの将来のリビジョンで最適化できるようになります。

どうやって回避すればいいの?

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

もし、既存のヘルパー関数ではサポートされていない新しい方法でいくつかのプロミスを作成する必要性を見つけた場合、避けられないDeferredsを使って自分自身の関数を書くことが最後の選択肢になるはずです。より機能的なライブラリに乗り換えることを検討するか、現在のライブラリに対してバグを報告してください。メンテナは既存の関数から構成を導き出したり、あなたのために新しいヘルパー関数を実装したり、あるいは処理する必要があるエッジケースを特定するのを手伝ったりすることができるはずです。