Le eccezioni non dovrebbero essere generate in Node.js?

2

Ho appena iniziato a lavorare su un nuovo progetto e nessuno lancia eccezioni qui. Ho capito che non dovresti farlo per il codice asincrono, ma sembra essere un modo valido per fare cose per il lato sincrono delle cose.

In genere farei qualcosa del genere:

function(thing, cb) {
  async_funct(thing, function(stuff) {
    let other_stuff = sync_thing(stuff)
    try {
      let more_other_stuff = other_sync_thing(other_stuff)
    } catch(err) {
      cb(err)
    }
    cb(null, err)
  })
}

Dove come nel progetto verrebbe trattato in questo modo:

function(thing, cb) {
  async_funct(thing, function(stuff) {
    let other_stuff = sync_thing(stuff)

    let more_other_stuff = other_sync_thing(other_stuff)
    if(more_other_stuff instanceof Error)
      cb(err)

    cb(null, err)
  })
}

Questa è una cosa standard? Sono interessato a sapere come gli altri stanno gestendo questo tipo di cose e perché.

    
posta unflores 08.06.2017 - 11:48
fonte

4 risposte

1

Mentre la risposta di Neil è abbastanza accurata, le cose potrebbero cambiare in futuro con l'avvento di funzioni asincrone in ECMAScript 8. Il supporto per le funzioni asincrone è stato aggiunto nel nodo 7.6 e nel nodo 8 (È). Inoltre, il Nodo 8 ha aggiunto la funzione util.promisify che apre molte potenzialità.

Lanciare un errore in una funzione asincrona equivale a rifiutare una promessa. Invece di avere diversi modi di gestire gli errori ( catch per le funzioni sincrone, (err, ...args) per i callback e .catch() per le promesse), il codice asincrono cerca sincrono e il costrutto try / catch può essere usato.

Ad esempio:

Utilizzo delle promesse, gestione degli errori asincroni con .catch e errori di sincronizzazione con try / catch:

const promisify = require('util').promisify
const fs = require('fs')
const readFileAsync = promisify(fs.readFile)

readFileAsync('foo.txt', {encoding: 'utf8'})
  .then(data => {
    console.log('CONTENT:', data)

    try {
      let moreOtherStuff = syncThing(data)
      return Promise.resolve(otherSyncThing(moreOtherStuff))
    } catch (err) {
      return Promise.reject(err)
    }
  })
  .catch(err => console.error(err))

Utilizzo delle funzioni asincrone, gestione degli errori con try / catch:

const promisify = require('util').promisify
const fs = require('fs')
const readFileAsync = promisify(fs.readFile)

(async () => {
  try {
    let data = await readFileAsync('foo.txt', {encoding: 'utf8'})
    console.log('CONTENT:', data)
    let moreOtherStuff = syncThing(data)
    return otherSyncThing(moreOtherStuff)
  } catch (err) {
    console.error(err)
    throw new Error(err)
  }
})()

Come puoi vedere, il codice usa lo stesso blocco try / catch per gestire l'errore (indipendentemente dal fatto che ciò sia desiderabile dipende dal caso d'uso e dalle preferenze personali). Indipendentemente da ciò, il codice sembra più sincrono quando viene letto dall'alto verso il basso.

Oltre a tutto questo, le dichiarazioni try / catch / finally non potevano essere ottimizzate precedente alla V8 5.3 (il motore JavaScript utilizzato nel Nodo 7.x), quindi le prestazioni potrebbero essere un fattore. Tuttavia, questo non dovrebbe più essere il caso, visto che ora il motore è in grado di ottimizzare questi blocchi.

    
risposta data 09.06.2017 - 08:57
fonte
2

Questo tipo di cose non è standard, almeno non nei progetti in cui ho lavorato.

La mia ipotesi è che le eccezioni modificano il flusso altrimenti normale del codice così com'è. Restituire un errore è, in un certo senso, come circondare ogni chiamata con una cattura di prova. Se viene generato un errore, potrebbe potenzialmente bypassare il codice che deve essere eseguito prima di restituire un errore.

Certo, questa è la migliore pratica non . dovresti essere lanciando e catturando le eccezioni se sono generalmente delle eccezioni e il codice dovrebbe essere preparato in modo appropriato per gestire queste eccezioni ovunque sia presente un blocco di prova nello stack delle chiamate. Le funzioni asincrone sono un po 'più difficili da gestire con errori, ma certamente non impossibili.

Nel tuo caso, potrebbe non esserci una differenza di comportamento, anche se generalmente queste cose vanno, se chiedi a un project manager se è possibile apportare un cambiamento importante a un progetto che potrebbe potenzialmente inserire bug che vanno contro il generale accettato norma del progetto in questione per l'ambito di nessuna funzionalità aggiunta o beneficio diverso da essere strutturati un po 'meglio, la risposta è di solito no. ;)

    
risposta data 08.06.2017 - 14:43
fonte
2

Il modo in cui dici che lo faresti normalmente è come lo farei anch'io, e in che modo la maggior parte delle librerie principali del nodo si aspetta che tu lo faccia.

Il codice sincrono può e genera eccezioni, mentre il codice asincrono può essere emettitore di eventi (nel qual caso devi ascoltare l'evento "errore") o avere un callback (nel qual caso si tratta di un modello di errore) o essere basato su Promessa (che ha .then() e .catch() ).

In tutti i casi tranne il callback (purtroppo), non riuscendo a rilevare un errore (o b / c non stai ascoltando per l'evento 'error' o non usi .catch() per gestire una promessa revocata, o non usare try..catch comporterà l'uscita dall'applicazione con un codice di errore in fase di runtime.

È sicuramente lo schema corretto per fare qualcosa di simile:

function myFunc(input, callback) {
    let bob = null;
    try{ 
      bob = someSyncFn();
    } catch (e) {
      return callback(e);
    }
    return callback(null, bob);
}

Tuttavia, concettualmente, se sei in grado di gestire l'errore nell'istruzione catch (ad esempio, se l'operazione sta aggiungendo dati aggiuntivi e non ti interessa davvero se è aggiunta o meno), potresti ingerire quell'errore , anche se raramente nella pratica. Come minimo, registrerò un avviso se lo faccio.

L'altro caso che è un po 'diverso è che in Express avrò un gestore di errori che è responsabile della segnalazione di commenti di errore disinfettati al client. Tale gestore di errori (un middleware) registrerà l'errore reale, res.send() un errore "pulito" (completo di codice di stato http) sul client e quindi ingerirà l'errore in modo che l'app non si arresti.

    
risposta data 09.06.2017 - 01:13
fonte
0

In JavaScript, non hai le eccezioni controllate dal compilatore, quindi non ci sono protezioni per garantire che i chiamanti gestiscano le eccezioni. Per questo motivo penso che sia una pratica migliore restituire un oggetto che è un errore o un risultato. In questo modo costringi il chiamante ad affrontare il potenziale di errore. Questo dovrebbe essere adeguatamente documentato e ovvio per il chiamante. Non penso che sia molto ovvio nel tuo secondo frammento di codice perché implica il controllo del tipo restituito. Qualcosa come Either di monet.js è, a mio parere, una soluzione migliore.

Esempio (utilizzando monet.js):

/**
 * @return {Either[Error,Int]} A random number or an error if the random was zero
 */
function other_sync_thing() {
    const rand = Math.random()
    if (rand === 0) {
        Either.Left(new Error("random crash"))
    }
    return Either.Right(rand)
}

function(thing, cb) {
  async_funct(thing, function(stuff) {
    let other_stuff = sync_thing(stuff)
    // We can either catch and log the error like so:
    other_sync_thing(other_stuff).cata(err => console.err(err), result => cb(result))
    // Or we can bubble the either up to the callback for it to handle like so:
    cb(other_sync_thing(other_stuff))
  }
} 
    
risposta data 09.06.2017 - 00:10
fonte

Leggi altre domande sui tag