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
- tilordne en standardverdi til variabelen i
- returner tidlig eller kast på nytt et unntak fra catch-blokken
- angi et flagg om
catch-blokken fanget et unntak, og sjekk det i enif-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.
