javascript - Korrekt Try...Catch-syntax med Async/Await
Jag gillar plattheten hos den nya Async/Await-funktionen som finns i Typescript, etc. Jag är dock inte säker på om jag gillar att jag måste deklarera variabeln jag await:ar utanför ett try...catch-block för att kunna använda den senare. Så här:
let createdUser
try {
createdUser = await this.User.create(userInfo)
} catch (error) {
console.error(error)
}
console.log(createdUser)
// business
// logic
// goes
// here
Rätta mig om jag har fel, men det verkar vara bästa praxis att inte placera flera affärslogikrader i try-blocket, så jag har bara alternativet att deklarera createdUser utanför blocket, tilldela det i blocket och sedan använda det efteråt.
Vad är bästa praxis i detta fall?
Svar #1
Det verkar vara bästa praxis att inte placera flera rader av affärslogik i try-kroppen
Faktiskt skulle jag säga att det är det. Du vill vanligtvis catch alla undantag när du arbetar med värdet:
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
}
Om du vill fånga och hantera fel endast från löftet har du tre val:
Deklarera variabeln utanför och grena beroende på om det uppstod ett undantag eller inte. Det kan ta olika former, som
- tilldela ett standardvärde till variabeln i
catch-blocket
- tilldela ett standardvärde till variabeln i
- returnera tidigt eller kasta om ett undantag från catch-blocket
- sätt en flagga om
catch-blocket fångade ett undantag, och testa det i ettif-villkor - testa värdet på variabeln för att se om det har tilldelats
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
}
Testa den fångade undantaget för dess typ, och hantera eller kasta om det baserat 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;
}
}
Tyvärr har standard JavaScript (fortfarande) ingen syntaxstöd för villkorliga undantag.
Om din metod inte returnerar löften som avvisas med tillräckligt specifika fel, kan du göra det själv genom att kasta om något mer lämpligt i en .catch()-hanterare:
try {
const createdUser = await this.User.create(userInfo).catch(err => {
throw new CreationError(err.message, {code: "USER_CREATE"});
});
…
} …
Se även Hantering av flera fångster i en promise-kedja för versionen före async/await av detta.
Använd then med två callbacks istället för try/catch. Detta är verkligen det minst fula sättet och min personliga rekommendation även för dess enkelhet och korrekthet, utan att förlita sig på taggade fel eller utseendet på resultatvärdet för att skilja mellan uppfyllelse och avslag 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
});
Självklart kommer det med nackdelen att införa callback-funktioner, vilket betyder att du inte lika enkelt kan break/continue loopar eller göra tidiga return från den yttre funktionen.
Svar #2
Ett annat enklare tillvägagångssätt är att lägga till .catch till promise-funktionen. t.ex:
const createdUser = await this.User.create(userInfo).catch( error => {
// handle error
})
Svar #3
Renare kod
använder async/await med Promise catch-hanterare.
Från vad jag ser har detta varit ett långvarigt problem som har stört (båda betydelserna) många programmerare och deras kod. Promise .catch är verkligen inte annorlunda än try/catch.
ES6 Promises catch-hanterare och fungerar harmoniskt med "await/async", vilket ger en korrekt lösning och renare kod:
const createUser = await this.User
.create(userInfo)
.catch(error => console.error(error))
console.log(createdUser)
// business
// logic
// goes
// here
Observera att även om detta besvarar frågan, så slukar det felet. I det här fallet är det bättre att kasta eftersom (1) denna operation (att skapa en användare) förväntas inte misslyckas, och (2) du kan inte fortsätta ändå - operationen har kraschat:
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
Att lära sig catch verkar inte vara värt det?
Renlighetsfördelarna kanske inte är uppenbara ovan, men de summeras i verkliga komplexa asynkrona operationer.
Som en illustration, förutom att skapa användare (this.User.create), kan vi skicka avisering (this.pushNotification) och skicka e-post (this.sendEmail).
denna.Användare.skapa
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 och 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
})
Skriv ner operationerna:
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. Och det är tydligt vilka fel du hanterar.
