javascript - Sintaxă corectă Try...Catch folosind Async/Await


Îmi place aspectul plat al noii funcționalități Async/Await disponibile în Typescript, etc. Totuși, nu sunt sigur că îmi place faptul că trebuie să declar variabila pe care o await în afara unui bloc try...catch pentru a o folosi mai târziu. Așa:

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

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

Te rog corectează-mă dacă greșesc, dar se pare că este o practică mai bună să nu plasezi mai multe linii de logică de afaceri în corpul try, așa că rămân doar cu alternativa de a declara createdUser în afara blocului, de a-l atribui în bloc și apoi de a-l folosi după.

Care este cea mai bună practică în acest caz?

Răspuns #1

Se pare că este cea mai bună practică să nu plasezi mai multe linii de logică de afaceri în corpul try

De fapt, aș spune că este. De obicei, vrei să prinzi toate excepțiile care apar atunci când lucrezi cu valoarea:

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
}

Dacă vrei să prinzi și să gestionezi erorile doar din promisiune, ai trei opțiuni:

  • Declară variabila în exterior și ramifică în funcție de faptul dacă a existat sau nu o excepție. Aceasta poate lua diverse forme, precum

    • atribuie o valoare implicită variabilei în blocul catch
    • returnează devreme sau re-aruncă o excepție din blocul catch
    • setează un flag dacă blocul catch a prins o excepție și testează-l într-o condiție if
    • test pentru valoarea variabilei pentru a fi fost atribuită
      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
      }
    
  • Testează excepția capturată pentru tipul său și gestioneaz-o sau arunc-o din nou în funcție de acesta.

      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;
          }
      }
    

    Din păcate, JavaScript-ul standard (încă) nu are suport sintactic pentru excepții condiționale.

    Dacă metoda ta nu returnează promisiuni care sunt respinse cu erori suficient de specifice, poți face asta tu însuți re-aruncând ceva mai potrivit într-un handler .catch():

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

    Vezi de asemenea Gestionarea mai multor capturi în lanțul de promisiuni pentru versiunea pre-async/await a acestuia.

  • Folosește then cu două callback-uri în loc de try/catch. Aceasta este cu adevărat cea mai puțin urâtă metodă și recomandarea mea personală, datorită simplității și corectitudinii, fără a se baza pe erori etichetate sau pe aspectul valorii rezultatului pentru a distinge între îndeplinirea și refuzul promisiunii:

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

    Desigur, vine cu dezavantajul introducerii funcțiilor callback, ceea ce înseamnă că nu poți să folosești la fel de ușor break/continue în bucle sau să faci return timpuriu din funcția exterioară.

Răspunsul #2

O altă abordare mai simplă este să adaugi .catch la funcția promisă. ex:

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

Răspunsul #3

Cod mai curat

utilizarea async/await cu un handler Promise catch.

Din câte văd, aceasta a fost o problemă de lungă durată care i-a deranjat (în ambele sensuri) pe mulți programatori și codul lor. .catch al Promise nu diferă cu nimic de try/catch.

Handlerul catch al Promise-ului ES6 funcționează în armonie cu „await/async”, oferind o soluție corectă și un cod mai curat:

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

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

Rețineți că, deși acest lucru răspunde la întrebare, acesta absoarbe eroarea. În acest caz, este mai bine să aruncați o excepție deoarece (1) această operațiune (crearea unui utilizator) nu se așteaptă să eșueze și (2) oricum nu puteți continua — operațiunea a eșuat:

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

Învățarea catch nu pare să merite?

Beneficiile legate de curățenie s-ar putea să nu fie evidente la suprafață, dar se adună în operațiuni asincrone complexe din lumea reală.

Ca exemplu, pe lângă crearea utilizatorului (this.User.create), putem trimite o notificare (this.pushNotification) și un email (this.sendEmail).

this.User.create

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 și 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
})

Composează operațiunile:

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
})

Fără try/catch. Și este clar ce erori gestionezi.