Quando .then (successo, fallimento) è considerato un antipattern per le promesse?


188

Ho dato un'occhiata alle FAQ sulla promessa dell'uccellino azzurro , in cui menziona che .then(success, fail)è un antipasto . Non capisco bene la sua spiegazione per quanto riguarda il tentativo di cattura. Cosa c'è di sbagliato in questo il seguente?

some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })

Sembra che l'esempio suggerisca quanto segue nel modo corretto.

some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })

Qual è la differenza?


1
then().catch()è più leggibile, in quanto non è necessario cercare la virgola e indagare è questo callback per il ramo di successo o fallimento.
Krzysztof Safjanowski il

7
@KevinB: C'è molta differenza, controlla le risposte
Bergi,

12
@KrzysztofSafjanowski - devastato dall'argomento "sembra migliore". Totalmente sbagliato!
Andrey Popov,

6
NOTA: quando si utilizza .catch, non si conosce quale passaggio ha causato il problema, all'interno dell'ultimo theno da qualche altra parte nella catena della promessa. Quindi ha il suo svantaggio.
vitaly-t,

2
Aggiungo sempre nomi di funzione ai parametri di promessa .then () per renderlo leggibile, cioèsome_promise_call() .then(function fulfilled(res) { logger.log(res) }, function rejected(err) { logger.log(err) })
Shane Rowatt,

Risposte:


215

Qual è la differenza?

La .then()chiamata restituirà una promessa che verrà rifiutata nel caso in cui la richiamata generi un errore. Ciò significa che, in caso di esito loggernegativo, l'errore verrebbe trasmesso al seguente .catch()callback, ma non al failcallback associato success.

Ecco un diagramma di flusso di controllo :

diagramma di flusso di controllo di allora con due argomenti diagramma di flusso di controllo della catena di cattura

Per esprimerlo in codice sincrono:

// some_promise_call().then(logger.log, logger.log)
then: {
    try {
        var results = some_call();
    } catch(e) {
        logger.log(e);
        break then;
    } // else
        logger.log(results);
}

Il secondo log(che è come il primo argomento .then()) verrà eseguito solo nel caso in cui non si sia verificata alcuna eccezione. Il blocco etichettati e la breakdichiarazione si sentono un po 'strano, questo è in realtà ciò che Python ha try-except-elseper (lettura consigliata!).

// some_promise_call().then(logger.log).catch(logger.log)
try {
    var results = some_call();
    logger.log(results);
} catch(e) {
    logger.log(e);
}

Il catchlogger gestirà anche le eccezioni dalla chiamata del logger di successo.

Questo per la differenza.

Non capisco bene la sua spiegazione per quanto riguarda il tentativo di cattura

L'argomento è che di solito si desidera rilevare errori in ogni fase dell'elaborazione e che non è necessario utilizzarlo in catene. L'aspettativa è che tu abbia un solo gestore finale che gestisca tutti gli errori, mentre, quando usi "antipattern", gli errori in alcuni dei callback non vengono gestiti.

Tuttavia, questo modello è in realtà molto utile: quando si desidera gestire gli errori che si sono verificati esattamente in questo passaggio e si desidera fare qualcosa di completamente diverso quando non si è verificato alcun errore, ovvero quando l'errore è irrecuperabile. Essere consapevoli del fatto che questo sta ramificando il flusso di controllo. Certo, a volte questo è desiderato.


Cosa c'è di sbagliato in questo il seguente?

some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })

Che hai dovuto ripetere il tuo callback. Preferisci

some_promise_call()
   .catch(function(e) {
       return e; // it's OK, we'll just log it
   })
   .done(function(res) {
       logger.log(res);
   });

Si potrebbe anche considerare l'utilizzo .finally()per questo.


7
questa è la spiegazione più utile che ho letto in pochi giorni (e ho letto molto). Non riesco a spiegare quanto sono grato! :) Penso che si dovrebbe sottolineare di più la differenza tra i due, che .catchsarà individuare gli errori anche all'interno della funzione di successo .. Personalmente, trovo questo estremamente sbagliato, come si finisce con un punto di errore di immissione, che può diventare più errori da più azioni, ma questo è il mio problema. Comunque - grazie per le informazioni! Non hai qualche strumento di comunicazione online che sei disposto a condividere, così posso chiedere qualche altra cosa? : P
Andrey Popov,

2
Spero che questo ti stia dando qualche altro voto qui. Sicuramente una delle migliori spiegazioni di un Promisemeccanico importante su questo sito.
Patrick Roberts,

2
.done()non fa parte dello standard, vero? Almeno MDN non elenca quel metodo. Sarebbe utile
giovedì

1
@ygoe Indeed. doneè una cosa Bluebird che è stata sostanzialmente deprecata da then+ rilevamento del rifiuto non gestito.
Bergi

1
solo una nota da un daltonico: i diagrammi non hanno senso :)
Benny K

37

I due non sono del tutto identici. La differenza è che il primo esempio non rileva un'eccezione generata nel successgestore. Quindi, se il tuo metodo dovrebbe restituire solo promesse risolte, come spesso accade, hai bisogno di un catchgestore finale (o ancora un altro thencon un successparametro vuoto ). Certo, è possibile che il thengestore non faccia nulla che possa potenzialmente fallire, nel qual caso l'utilizzo di un parametro 2 thenpotrebbe andare bene.

Ma credo che il punto del testo a cui ti sei collegato sia che thenè principalmente utile rispetto ai callback nella sua capacità di concatenare un mucchio di passaggi asincroni e, quando lo fai effettivamente, la forma a 2 parametri di thensottilmente non si comporta esattamente come previsto , per il motivo sopra. È particolarmente controintuitivo quando viene utilizzato a catena centrale.

Come qualcuno che ha fatto molte cose complesse asincrone e si è imbattuto in angoli come questo più di quanto io voglia ammettere, consiglio vivamente di evitare questo anti-schema e seguire l'approccio del gestore separato.


18

Osservando i vantaggi e gli svantaggi di entrambi possiamo fare un'ipotesi calcolata su quale sia appropriato per la situazione. Questi sono i due approcci principali per attuare le promesse. Entrambi hanno i suoi vantaggi e svantaggi

Approccio alla cattura

some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })

vantaggi

  1. Tutti gli errori sono gestiti da un blocco catch.
  2. Cattura anche qualsiasi eccezione nel blocco allora.
  3. Concatenamento di più callback di successo

svantaggi

  1. In caso di concatenamento diventa difficile mostrare diversi messaggi di errore.

Approccio di successo / errore

some_promise_call()
.then(function success(res) { logger.log(res) },
      function error(err) { logger.log(err) })

vantaggi

  1. Ottieni un controllo degli errori dettagliato.
  2. Puoi avere una funzione comune di gestione degli errori per varie categorie di errori come errore db, errore 500 ecc.

svantaggi

  1. Ne occorrerà ancora un altro catchse si desidera gestire gli errori generati dal callback di successo

Per chi ha bisogno di eseguire il debug di problemi di produzione utilizzando solo un file di registro, preferisco l'approccio di successo / errore in quanto offre la possibilità di creare una catena di errori causale che può essere registrata ai limiti di uscita della tua app.
Shane Rowatt,

domanda. diciamo che faccio una chiamata asincrona che fa una delle seguenti cose: 1) restituisce correttamente (codice 2xx statusc), 2) restituisce senza successo (codice 4xx o 5xx) ma non viene rifiutato di per sé, 3) o non ritorna affatto ( la connessione a Internet non è attiva). Per il caso n. 1, viene richiamata la richiamata riuscita nel .then. Per il caso n. 2, viene richiamata la richiamata dell'errore nel .then. Per il caso n. 3, viene chiamato .catch. Questa è un'analisi corretta, giusto? Il caso n. 2 è tecnicamente più complicato perché tecnicamente un 4xx o 5xx non è un rifiuto, ma restituisce comunque con successo. Pertanto, dobbiamo gestirlo all'interno del .then. .... La mia comprensione è corretta?
Benjamin Hoffman,

"Per il caso n. 2, viene richiamato l'errore di richiamata nel .then. Per il caso n. 3, viene chiamato il .catch. Questa è un'analisi corretta, giusto?" - Ecco come funziona il recupero
aWebDeveloper,

2

Semplice spiegazione:

In ES2018

Quando viene chiamato il metodo catch con argomento onRejected, vengono eseguite le seguenti operazioni:

  1. Lascia che la promessa sia questo valore.
  2. Ritorno ? Invocare (promessa, "allora", «indefinito, rifiutato»).

questo significa:

promise.then(f1).catch(f2)

è uguale a

promise.then(f1).then(undefiend, f2)

1

L'utilizzo .then().catch()consente di abilitare Concise Chaining, necessario per soddisfare un flusso di lavoro. Potrebbe essere necessario leggere alcune informazioni dal database, quindi si desidera passarle a un'API asincrona, quindi si desidera manipolare la risposta. È possibile che si desideri reinserire la risposta nel database. Gestire tutti questi flussi di lavoro con il tuo concetto è fattibile ma molto difficile da gestire. La soluzione migliore sarà quella then().then().then().then().catch()che riceve tutti gli errori in una sola volta e ti consente di mantenere la manutenibilità del codice.


0

L'uso then()e catch()aiuta a gestire il successo e il fallimento sulla promessa. catch()funziona su promessa restituita da then(). Gestisce,

  1. Se la promessa è stata respinta. Vedi # 3 nella foto
  2. Se si è verificato un errore nel gestore del successo di then (), tra i numeri di riga da 4 a 7 di seguito. Vedere # 2.a nell'immagine (la richiamata then()non riuscita non gestisce questo.)
  3. Se si è verificato un errore nel gestore errori di allora (), riga numero 8 di seguito. Vedi # 3.b nella foto.

1. let promiseRef: Promise = this. aTimetakingTask (false); 2. promiseRef 3. .then( 4. (result) => { 5. /* successfully, resolved promise. 6. Work on data here */ 7. }, 8. (error) => console.log(error) 9. ) 10. .catch( (e) => { 11. /* successfully, resolved promise. 12. Work on data here */ 13. });

inserisci qui la descrizione dell'immagine

Nota : molte volte, il gestore degli errori potrebbe non essere definito se catch()è già stato scritto. EDIT: reject()provoca il richiamo catch()solo se il gestore errori in nonthen() è definito. Avviso n. 3 nella foto per il . Viene richiamato quando i gestori nelle righe n. 8 e 9 non sono definiti.catch()

Ha senso perché la promessa restituita da then()non ha un errore se un callback se ne occupa.


La freccia dal numero 3 al catchcallback sembra sbagliata.
Bergi,

Grazie! Con un callback di errore definito in then (), non viene richiamato (riga n. 8 e n. 9 nello snippet di codice). # 3 invoca una delle due frecce. Ha senso perché promessa restituita da allora () non ha un errore se un callback se ne occupa. Modificata la risposta!
VenCKi,

-1

Invece di parole, buon esempio. Codice seguente (se la prima promessa è stata risolta):

Promise.resolve()
.then
(
  () => { throw new Error('Error occurs'); },
  err => console.log('This error is caught:', err)
);

è identico a:

Promise.resolve()
.catch
(
  err => console.log('This error is caught:', err)
)
.then
(
  () => { throw new Error('Error occurs'); }
)

Ma con la prima promessa respinta, questo non è identico:

Promise.reject()
.then
(
  () => { throw new Error('Error occurs'); },
  err => console.log('This error is caught:', err)
);

Promise.reject()
.catch
(
  err => console.log('This error is caught:', err)
)
.then
(
  () => { throw new Error('Error occurs'); }
)

4
Questo non ha senso, puoi per favore rimuovere questa risposta? È fuorviante e distrae dalla risposta corretta.
Andy Ray,

@AndyRay, questo non ha senso nella vera applicazione, ma ha senso capire il lavoro delle promesse.
ktretyak,
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.