Jaki jest jednoznaczny obietnica budowy antipattern i jak go uniknąć?

516

Pisałem kod, który robi coś, co wygląda:

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() */ |     });
}                                        | }

Ktoś mi powiedział, że nazywa się to odpowiednio „ odroczonym antipatternem ” lub „ Promisekonstruktorem antipatternem ”, co jest złego w tym kodzie i dlaczego nazywa się to antyatternem ?

Benjamin Gruenbaum
źródło
Czy mogę potwierdzić, że usunięcie tego (w kontekście prawej, a nie lewej, przykład) usuwa getStuffDoneopakowanie funkcji i po prostu używa literału Promise?
The Dembinski
1
czy ma catchblok w getStuffDoneopakowaniu antipattern?
The Dembinski
1
Przynajmniej dla Promiseprzykładu natywnego masz również niepotrzebne opakowania funkcji dla programów obsługi .theni .catchobsługi (tzn. Może to być po prostu .then(resolve).catch(reject).) Idealna burza anty-wzorów.
Noah Freitas
6
@NoahFreitas ten kod jest napisany w ten sposób do celów dydaktycznych. Napisałem to pytanie i odpowiedź, aby pomóc osobom, które napotkają ten problem po przeczytaniu dużej ilości kodu wyglądającego w ten sposób :)
Benjamin Gruenbaum
Zobacz także stackoverflow.com/questions/57661537/…, aby dowiedzieć się, jak wyeliminować nie tylko jawną konstrukcję Promise, ale także użycie zmiennej globalnej.
David Spector,

Odpowiedzi:

357

Odroczone antywzorzec projektowy (obecnie wyraźne-budowa antywzorzec projektowy) wymyślone przez Esailija jest wspólne antywzorzec projektowy ludzi, którzy są nowicjuszami w obietnice zrobić, zrobiłem to sam, kiedy po raz pierwszy użyty obietnic. Problem z powyższym kodem polega na tym, że nie wykorzystuje się tego, co obiecuje łańcuch.

Obietnice mogą wiązać się z łańcuchem .theni możesz bezpośrednio zwrócić obietnice. Twój kod getStuffDonemoże zostać przepisany jako:

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

Obietnice polegają na zwiększeniu czytelności kodu asynchronicznego i zachowaniu się jak kod synchroniczny bez ukrywania tego faktu. Obietnice reprezentują abstrakcję nad wartością jednorazowej operacji, abstrakcyjnie pojmują wyrażenie lub wyrażenie w języku programowania.

Powinieneś używać odroczonych obiektów tylko wtedy, gdy konwertujesz API na obietnice i nie możesz tego robić automatycznie, lub gdy piszesz funkcje agregacji, które łatwiej wyrazić w ten sposób.

Cytując Esailija:

Jest to najczęstszy anty-wzór. Łatwo się w to wpaść, gdy tak naprawdę nie rozumiesz obietnic i nie myślisz o nich jako o świetnych emiterach zdarzeń lub narzędziu do oddzwaniania. Podsumujmy: obietnice dotyczą tego, że kod asynchroniczny zachowa większość utraconych właściwości kodu synchronicznego, takich jak wcięcie płaskie i jeden kanał wyjątków.

Benjamin Gruenbaum
źródło
@BenjaminGruenbaum: Jestem przekonany, że wykorzystam do tego odroczenie, więc nie potrzebuję nowego pytania. Pomyślałem, że to przypadek, w którym brakowało ci odpowiedzi. To, co robię, wydaje się bardziej przeciwieństwem agregacji, prawda?
mhelvens
1
@mhelvens Jeśli ręcznie dzielisz interfejs API bez wywołania zwrotnego na interfejs API obietnicy, który pasuje do części „Konwertowanie interfejsu API wywołania zwrotnego na obietnice”. Antypattern polega na zawarciu obietnicy inną obietnicą bez uzasadnionego powodu, nie zawiązujesz obietnicy na początek, więc nie ma ona zastosowania tutaj.
Benjamin Gruenbaum,
@BenjaminGruenbaum: Ach, myślałem, że same odroczenie było uważane za anty-wzorzec, co w przypadku Bluebird go deprecjonuje, a ty wspominasz o „przekształceniu API w obietnice” (co jest również przypadkiem nie zawarcia obietnicy na początek).
mhelvens
@mhelvens Wydaje mi się, że nadmierny odroczony wzór anty byłby dokładniejszy w stosunku do tego, co faktycznie robi. Bluebird wycofał .defer()interfejs API do nowszego (i rzucił bezpiecznie) konstruktora obietnic, nie przestał (w żaden sposób) odrzucać pojęcia konstruowania obietnic :)
Benjamin Gruenbaum
1
Dziękuję @ Roamer-1888 Twoje referencje pomogły mi w końcu dowiedzieć się, co było moim problemem. Wygląda na to, że tworzyłem zagnieżdżone (niezwrócone) obietnice, nie zdając sobie z tego sprawy.
ghuroo
134

Co jest z tym nie tak?

Ale wzór działa!

Szczęściarz. Niestety prawdopodobnie tak nie jest, ponieważ prawdopodobnie zapomniałeś o jakiejś sprawie krawędziowej. W ponad połowie przypadków, które widziałem, autor zapomniał zająć się obsługą błędów:

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

Jeśli druga obietnica zostanie odrzucona, stanie się to niezauważona, a nie zostanie propagowana do nowej obietnicy (gdzie zostanie zrealizowana) - a nowa obietnica pozostanie na zawsze w oczekiwaniu, co może spowodować wycieki.

To samo dzieje się w przypadku, gdy kod wywołania zwrotnego powoduje błąd - np. Gdy resultnie ma propertyznaku i zostanie zgłoszony wyjątek. To pozostanie nierozstrzygnięte i pozostawi nową obietnicę nierozstrzygniętą.

W przeciwieństwie do tego, użycie .then()automatycznie zajmuje się tymi dwoma scenariuszami i odrzuca nową obietnicę, gdy wystąpi błąd:

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

Odroczony antypattern jest nie tylko uciążliwy, ale również podatny na błędy . Korzystanie .then()z łańcuchów jest znacznie bezpieczniejsze.

Ale załatwiłem wszystko!

Naprawdę? Dobrze. Będzie to jednak dość szczegółowe i obfite, zwłaszcza jeśli korzystasz z biblioteki obietnic, która obsługuje inne funkcje, takie jak anulowanie lub przekazywanie wiadomości. A może tak będzie w przyszłości, a może chcesz zamienić swoją bibliotekę na lepszą? W tym celu nie będziesz chciał przepisać kodu.

Metody bibliotek ( then) nie tylko natywnie obsługują wszystkie funkcje, ale mogą także mieć pewne optymalizacje. Korzystanie z nich prawdopodobnie przyspieszy kod lub przynajmniej pozwoli na optymalizację w przyszłych wersjach biblioteki.

Jak tego uniknąć?

Tak więc za każdym razem, gdy znajdziesz ręczne tworzenie Promiselub Deferredjuż istnieją obietnice, najpierw sprawdź interfejs API biblioteki . Odroczony antypattern jest często stosowany przez ludzi, którzy widzą obietnice [tylko] jako wzorzec obserwatora - ale obietnice są czymś więcej niż tylko wywołaniami zwrotnymi : powinny być możliwe do skomponowania. Każda przyzwoita biblioteka ma wiele łatwych w użyciu funkcji do komponowania obietnic w każdy możliwy do pomyślenia sposób, dbając o wszystkie rzeczy niskiego poziomu, z którymi nie chcesz sobie poradzić.

Jeśli zauważyłeś potrzebę złożenia obietnic w nowy sposób, który nie jest obsługiwany przez istniejącą funkcję pomocnika, pisanie własnej funkcji z nieuniknionymi Odroczeniami powinno być ostatnią opcją. Rozważ przejście na bardziej funkcjonalną bibliotekę i / lub zgłoś błąd w bieżącej bibliotece. Jego opiekun powinien być w stanie wyodrębnić kompozycję z istniejących funkcji, zaimplementować dla ciebie nową funkcję pomocniczą i / lub pomóc w identyfikacji przypadków skrajnych, które należy obsłużyć.

Bergi
źródło
Czy istnieją przykłady inne niż funkcja setTimeout, w których można zastosować konstruktor, ale nie można go uznać za „Promise constructor anitpattern”?
guest271314,
1
@ guest271314: Wszystko asynchroniczne, co nie zwraca obietnicy. Chociaż wystarczająco często osiągasz lepsze wyniki dzięki zaangażowanym pomocnikom bibliotek w promowaniu. I upewnij się, że zawsze obiecujesz na najniższym poziomie, więc nie jest to „ funkcja zawierającasetTimeout ”, ale „ sama funkcjasetTimeout ”.
Bergi,
„I upewnij się, że zawsze obiecujesz na najniższym poziomie, więc nie jest to„ funkcja obejmująca setTimeout”, ale„ setTimeoutsama funkcja ”„ Czy można opisać, powiązać z różnicami między nimi?
guest271314,
@ guest271314 Funkcja, która zawiera tylko wywołanie, setTimeoutwyraźnie różni się od samej funkcjisetTimeout , prawda?
Bergi,
4
Myślę, że jedną z ważnych lekcji tutaj, która nie została jak dotąd wyraźnie określona, ​​jest to, że obietnica i jej łańcuch „wtedy” reprezentują jedną operację asynchroniczną: operacja początkowa jest w konstruktorze obietnicy, a ostateczny punkt końcowy - następnie ”. Więc jeśli masz operację synchronizacji, a następnie operację asynchroniczną, umieść synchronizację w obietnicy. Jeśli masz operację asynchroniczną, po której następuje synchronizacja, umieść elementy synchronizacji w polu „wtedy”. W pierwszym przypadku zwróć oryginalną obietnicę. W drugim przypadku zwróć łańcuch Obietnica / potem (który jest również Obietnicą).
David Spector