javascript - Korrekt Try...Catch-syntaks ved bruk av Async/Await


Jeg liker flatheten til den nye Async/Await-funksjonen som er tilgjengelig i Typescript, osv. Men jeg er ikke sikker på om jeg liker det faktum at jeg må deklarere variabelen jeg awaiter på utenfor en try...catch-blokk for å kunne bruke den senere. Slik:

let createdUser
try {
    createdUser = await this.User.create(userInfo)
} catch (error) {
    console.error(error)
}

console.log(createdUser)
// business
// logic
// goes
// here

Vennligst korriger meg hvis jeg tar feil, men det ser ut til å være beste praksis ikke å plassere flere linjer med forretningslogikk i try-blokken, så jeg står igjen med bare alternativet å deklarere createdUser utenfor blokken, tilordne det i blokken, og deretter bruke det etterpå.

Hva er beste praksis i dette tilfellet?

Svar #1

Det ser ut til å være best praksis å ikke plassere flere linjer med forretningslogikk i try-blokken

Faktisk vil jeg si at det er det. Du ønsker vanligvis å fange alle unntak når du arbeider med verdien:

try {
    const createdUser = await this.User.create(userInfo);

    console.log(createdUser)
    // business logic goes here
} catch (error) {
    console.error(error) // from creation or business logic
}

Hvis du vil fange og håndtere feil bare fra løftet, har du tre valg:

  • Deklarer variabelen utenfor, og gren avhengig av om det var en unntakssituasjon eller ikke. Det kan ta ulike former, som

    • tilordne en standardverdi til variabelen i catch-blokken
    • returner tidlig eller kast på nytt et unntak fra catch-blokken
    • angi et flagg om catch-blokken fanget et unntak, og sjekk det i en if-betingelse
    • test for verdien av variabelen for å ha blitt tildelt
      let createdUser; // or use `var` inside the block
      try {
          createdUser = await this.User.create(userInfo);
      } catch (error) {
          console.error(error) // from creation
      }
      if (createdUser) { // user was successfully created
          console.log(createdUser)
          // business logic goes here
      }
    
  • Test den oppfangede unntaket for typen sin, og håndter eller kast det på nytt basert på det.

      try {
          const createdUser = await this.User.create(userInfo);
          // user was successfully created
          console.log(createdUser)
          // business logic goes here
      } catch (error) {
          if (error instanceof CreationError) {
              console.error(error) // from creation
          } else {
              throw error;
          }
      }
    

    Dessverre har standard JavaScript (fremdeles) ikke syntaksstøtte for betingede unntak.

    Hvis metoden din ikke returnerer løfter som avvises med spesifikke nok feil, kan du gjøre det selv ved å kaste noe mer passende på nytt i en .catch()-behandler:

      try {
          const createdUser = await this.User.create(userInfo).catch(err => {
              throw new CreationError(err.message, {code: "USER_CREATE"});
          });
          …
      } …
    

    Se også Håndtering av flere catch i løftekjede for versjonen før async/await av dette.

  • Bruk then med to callbacks i stedet for try/catch. Dette er virkelig den minst stygge måten, og også min personlige anbefaling på grunn av dens enkelhet og korrekthet, uten å stole på merkede feil eller utseendet til resultatverdien for å skille mellom oppfyllelse og avvisning av løftet:

      await this.User.create(userInfo).then(createdUser => {
          // user was successfully created
          console.log(createdUser)
          // business logic goes here
      }, error => {
          console.error(error) // from creation
      });
    

    Selvfølgelig kommer det med ulempen av å introdusere tilbakekallingsfunksjoner, noe som betyr at du ikke like lett kan break/continue løkker eller gjøre tidlige return-er fra den ytre funksjonen.

Svar #2

En annen enklere tilnærming er å legge til .catch på løftefunksjonen. f.eks:

const createdUser = await this.User.create(userInfo).catch( error => {
// handle error
})

Svar #3

Renere kode

bruker async/await med Promise catch-håndterer.

Fra det jeg ser, har dette vært et langvarig problem som har plaget (begge betydninger) mange programmere og koden deres. Promise .catch er egentlig ikke annerledes enn try/catch.

ES6 Promises catch-handler og fungerer harmonisk med «await/async», og gir en skikkelig løsning og renere kode:

const createUser = await this.User
    .create(userInfo)
    .catch(error => console.error(error))

console.log(createdUser)
// business
// logic
// goes
// here

Merk at selv om dette svarer på spørsmålet, så sluker det feilen. I dette tilfellet er det bedre å kaste en feil fordi (1) denne operasjonen (å opprette en bruker) ikke forventes å feile, og (2) du uansett ikke kan fortsette - operasjonen har feilet:

const createUser = await this.User
    .create(userInfo)
    .catch(error => {
        // do what you need with the error
        console.error(error)

        // maybe send to Datadog or Sentry

        // don't gobble up the error
        throw error
    })

console.log(createdUser)
// business
// logic
// goes
// here

Å lære catch virker ikke verdt det?

Fordelene med renhet kan ikke være åpenbare ovenfor, men de legger seg opp i komplekse asynkrone operasjoner i den virkelige verden.

Som et eksempel, i tillegg til å opprette bruker (this.User.create), kan vi sende push-varsel (this.pushNotification) og sende e-post (this.sendEmail).

denne.Bruker.lag

this.User.create = async(userInfo) => {

    // collect some fb data and do some background check in parallel
    const facebookDetails = await retrieveFacebookAsync(userInfo.email)
        .catch(error => {
            // we can do some special error handling

            // and throw back the error
         })
    const backgroundCheck = await backgroundCheckAsync(userInfo.passportID)

    if (backgroundCheck.pass !== true) throw Error('Background check failed')

    // now we can insert everything
    const createdUser = await Database.insert({ ...userInfo, ...facebookDetails })

    return createdUser
}

this.pushNotifcation og this.sendEmail

this.pushNotification = async(userInfo) => {
    const pushed = await PushNotificationProvider.send(userInfo)
    return pushed
})

this.sendEmail = async(userInfo) => {
    const sent = await mail({ to: userInfo.email, message: 'Welcome' })
    return sent
})

Sett sammen operasjonene:

const createdUser = await this.User
    .create(userInfo)
    .catch(error => {
        // handle error
    })

// business logic here

return await Promise.all([
    this.pushNotification(userInfo),
    this.sendEmail(userInfo)
]).catch(error => {
    // handle errors caused
    // by pushNotification or sendEmail
})

Ingen try/catch. Og det er klart hvilke feil du håndterer.

Relaterte artikler