Qual è l'antipattern di costruzione della promessa esplicita e come posso evitarlo?

May 22 2014

Stavo scrivendo 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 è chiamato rispettivamente " antipattern differito " o " Promiseantipattern del costruttore ", cosa c'è di male in questo codice e perché è chiamato antipattern ?

Risposte

380 BenjaminGruenbaum May 22 2014 at 17:07

L' anti-pattern differito (ora anti-pattern a costruzione esplicita) coniato da Esailija è un comune anti-pattern che non conosce le promesse, l'ho fatto io stesso quando ho usato per la prima volta le promesse. Il problema con il codice sopra è che non riesce a utilizzare il fatto che promette catena.

Le promesse possono concatenarsi .thene tu puoi restituire le promesse direttamente. Il tuo codice in getStuffDonepuò essere riscritto come:

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

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

È necessario utilizzare solo oggetti differiti quando si converte un'API in promesse e non è possibile farlo automaticamente o quando si scrivono funzioni di aggregazione che sono più facili da esprimere in questo modo.

Citando Esailija:

Questo è l'anti-pattern più comune. È facile cadere in questo quando non capisci veramente le promesse e le pensi come emettitori di eventi glorificati o utilità di callback. Ricapitoliamo: le promesse riguardano il fare in modo che il codice asincrono conservi la maggior parte delle proprietà perse del codice sincrono come il rientro piatto e un canale di eccezione.

142 Bergi Aug 29 2014 at 20:28

Che cosa c'è che non va?

Ma lo schema funziona!

Sei fortunato. Sfortunatamente, probabilmente non è così, poiché probabilmente hai dimenticato qualche 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 rifiutata, ciò avverrà inosservato invece di essere propagato alla nuova promessa (dove verrebbe gestita) - e la nuova promessa rimane per sempre in sospeso, il che può indurre fughe di notizie.

La stessa cosa accade nel caso in cui il codice di callback causi un errore, ad esempio quando resultnon ha un propertye viene generata un'eccezione. Ciò non verrebbe gestito e lascerebbe la nuova promessa irrisolta.

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'antipattern differito non è solo ingombrante, ma anche soggetto a errori . L'utilizzo .then()per il concatenamento è molto più sicuro.

Ma ho gestito tutto!

Veramente? Buono. Tuttavia, questo sarà piuttosto dettagliato e abbondante, soprattutto se si utilizza una libreria di promesse 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 alcune ottimizzazioni. Il loro utilizzo probabilmente renderà il tuo codice più veloce, o almeno consentirà di essere ottimizzato da future revisioni della libreria.

Come lo evito?

Pertanto, ogni volta che ti trovi a creare manualmente un Promiseo Deferrede sono coinvolte promesse già esistenti, controlla prima l'API della libreria . L'antipattern differito viene spesso applicato da persone che vedono le promesse [solo] come un modello di osservazione, ma le promesse sono più che richiami : dovrebbero essere componibili. Ogni libreria 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 riscontrato la necessità di comporre alcune promesse in un modo nuovo che non è supportato da una funzione di supporto esistente, scrivere la tua funzione con Deferred inevitabili dovrebbe essere la tua ultima opzione. Considera l'idea di passare a una libreria più ricca di funzionalità e / o segnala 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 te e / o aiutarti a identificare i casi limite che devono essere gestiti.