Qual è l'antipasto esplicito della costruzione della promessa e come posso evitarlo?


516

Stavo scrivendo un codice che fa qualcosa che assomiglia a:

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

Qualcuno mi ha detto che questo si chiama rispettivamente " antipattern differito " o " Promiseantipattern costruttore ", cosa c'è di male in questo codice e perché viene chiamato antipattern ?


Posso confermare che il take away di questo è (nel contesto dell'esempio di destra, non di sinistra) rimuovere il getStuffDonewrapper di funzione e usare solo il valore letterale Promise?
Il Dembinski il

1
o il catchblocco getStuffDonenell'involucro è l'antipasto?
Il Dembinski il

1
Almeno per l' Promiseesempio nativo hai anche involucri di funzioni non necessari per i gestori .thene .catch(cioè potrebbe essere .then(resolve).catch(reject)). Una tempesta perfetta di anti-schemi.
Noah Freitas,

6
@NoahFreitas quel codice è scritto in questo modo per scopi didattici. Ho scritto questa domanda e risposta per aiutare le persone che si imbattono in questo problema dopo aver letto un sacco di codice simile a quello :)
Benjamin Gruenbaum,

Vedi anche stackoverflow.com/questions/57661537/… per come eliminare non solo la costruzione Promise esplicita, ma anche l'uso di una variabile globale.
David Spector,

Risposte:


357

L' antipasto differito (ora anti-modello di costruzione esplicita) coniato da Esailija è un comune anti-modello che le persone che non conoscono le promesse fanno, l'ho fatto da solo quando ho usato le promesse. Il problema con il codice sopra è che non riesce a utilizzare il fatto che promette la catena.

Le promesse possono essere .thencollegate e puoi restituire direttamente le promesse. Il tuo codice in getStuffDonepuò essere riscritto come:

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

Le promesse riguardano il rendere il codice asincrono più leggibile e comportarsi come un codice sincrono senza nascondere questo fatto. Le promesse rappresentano un'astrazione rispetto al valore di una volta sola operazione, astraggono la nozione di una dichiarazione o espressione in un linguaggio di programmazione.

Dovresti usare oggetti differiti solo quando stai convertendo un'API in promesse e non puoi farlo automaticamente, o quando scrivi funzioni di aggregazione che sono più facili espresse in questo modo.

Citando Esailija:

Questo è l'anti-pattern più comune. È facile cadere in questo quando non capisci davvero le promesse e le pensi come emettitori di eventi glorificati o utilità di callback. Ricapitolando: le promesse riguardano il mantenimento del codice asincrono nella maggior parte delle proprietà perse del codice sincrono come l'indentazione piatta e un canale di eccezione.


@BenjaminGruenbaum: sono fiducioso nel mio uso dei differiti per questo, quindi non c'è bisogno di una nuova domanda. Ho solo pensato che fosse un caso d'uso che mancava alla tua risposta. Quello che sto facendo sembra più il contrario dell'aggregazione, no?
mhelvens,

1
@mhelvens Se stai dividendo manualmente un'API senza callback in un'API di promessa adatta alla parte "conversione di un'API di callback in promesse". L'antipasto riguarda il confezionamento di una promessa in un'altra promessa senza una buona ragione, non stai avvolgendo una promessa per cominciare quindi non si applica qui.
Benjamin Gruenbaum,

@BenjaminGruenbaum: Ah, anche se i rinvii stessi erano considerati un anti-pattern, con bluebird che li deprecava, e tu menzionavi "convertire un'API in promesse" (che è anche un caso di non concludere una promessa per cominciare).
mhelvens,

@mhelvens Immagino che l' anti schema differito in eccesso sarebbe più accurato per quello che effettivamente fa. Bluebird ha deprecato l' .defer()API nel nuovo (e gettato sicuro) costruttore di promesse, non ha (in nessun modo) deprecato l'idea di costruire promesse :)
Benjamin Gruenbaum,

1
Grazie @ Roamer-1888 il tuo riferimento mi ha aiutato finalmente a capire qual era il mio problema. Sembra che stavo creando promesse nidificate (non restituite) senza accorgermene.
ghuroo,

134

Che cosa c'è che non va?

Ma il modello funziona!

Sei fortunato. Sfortunatamente, probabilmente non lo è, come probabilmente hai dimenticato un caso limite. In più della metà delle occorrenze che ho visto, l'autore ha dimenticato di occuparsi del gestore degli errori:

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

Se l'altra promessa viene respinta, ciò accadrà inosservato invece di essere propagato alla nuova promessa (dove verrebbe gestita) - e la nuova promessa rimane in sospeso per sempre, il che può causare perdite.

La stessa cosa accade nel caso in cui il codice di callback provochi un errore, ad esempio quando resultnon propertyè presente un'eccezione. Ciò non verrebbe gestito e lascerebbe irrisolta la nuova promessa.

Al contrario, l'utilizzo si .then()occupa automaticamente di entrambi questi scenari e rifiuta la nuova promessa quando si verifica un errore:

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

L'antipasto differito non è solo ingombrante, ma anche soggetto a errori . L'uso .then()per il concatenamento è molto più sicuro.

Ma ho gestito tutto!

Veramente? Buona. Tuttavia, questo sarà abbastanza dettagliato e abbondante, soprattutto se si utilizza una libreria promessa che supporta altre funzionalità come la cancellazione o il passaggio di messaggi. O forse lo farà in futuro, o vuoi scambiare la tua libreria con una migliore? Non vorrai riscrivere il tuo codice per questo.

I metodi delle librerie ( then) non solo supportano nativamente tutte le funzionalità, ma potrebbero anche avere determinate ottimizzazioni. Il loro utilizzo renderà probabilmente il tuo codice più veloce o almeno consentirà di essere ottimizzato da future revisioni della libreria.

Come lo evito?

Quindi, ogni volta che ti ritrovi a creare manualmente una Promiseo Deferrede già esistenti promesse, controlla prima l'API della libreria . L'antipasto differito viene spesso applicato da persone che vedono le promesse [solo] come un modello di osservatore - ma le promesse sono più che richiami : dovrebbero essere compostabili. Ogni biblioteca decente ha molte funzioni facili da usare per la composizione delle promesse in ogni modo pensabile, prendendosi cura di tutte le cose di basso livello che non vuoi affrontare.

Se hai trovato la necessità di comporre alcune promesse in un modo nuovo che non è supportato da una funzione di aiuto esistente, scrivere la tua funzione con inevitabili rinvii dovrebbe essere l'ultima opzione. Considera di passare a una libreria più ricca di funzionalità e / o di presentare un bug sulla tua libreria corrente. Il suo manutentore dovrebbe essere in grado di derivare la composizione dalle funzioni esistenti, implementare una nuova funzione di supporto per l'utente e / o aiutare a identificare i casi limite che devono essere gestiti.


Ci sono esempi, oltre a una funzione inclusa setTimeout, in cui il costruttore potrebbe essere usato ma non essere considerato "Promessa costruttore anitpattern"?
ospite271314

1
@ guest271314: tutto asincrono che non restituisce una promessa. Anche se abbastanza spesso ottieni risultati migliori con gli aiutanti di promozione dedicati delle biblioteche. E assicurati di promettere sempre al livello più basso, quindi non è " una funzione compresasetTimeout ", ma " la funzione setTimeoutstessa ".
Bergi,

"E assicurati di promettere sempre al livello più basso, quindi non è" una funzione compresa setTimeout", ma" la funzione setTimeoutstessa "" Può descrivere, collegare alle differenze, tra i due?
ospite271314

@ guest271314 Una funzione che include solo una chiamata setTimeoutè chiaramente diversa dalla funzione setTimeoutstessa , non è vero?
Bergi,

4
Penso che una delle lezioni importanti qui, una che non è stata chiaramente dichiarata finora, è che una Promessa e il suo incatenato "allora" rappresenta un'operazione asincrona: l'operazione iniziale è nel costruttore Promessa e l'eventuale endpoint è nella " quindi 'funzione. Quindi, se hai un'operazione di sincronizzazione seguita da un'operazione asincrona, metti le cose di sincronizzazione nella Promessa. Se hai un'operazione asincrona seguita da una sincronizzazione, metti le cose di sincronizzazione in 'allora'. Nel primo caso, restituisci la Promessa originale. Nel secondo caso, restituisci la Promessa / quindi la catena (che è anche una Promessa).
David Spector,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.