Bir Sözü sadece hata durumları için reddetmek mi?


25

Diyelim ki bu fonksiyonun bir söz veren kimlik doğrulaması var. Söz, daha sonra sonucu ile çözer. Yanlış ve doğru, gördüğüm gibi beklenen sonuçlar ve reddedilmeler sadece bir hata durumunda gerçekleşmelidir. Yoksa, kimlik doğrulamadaki bir başarısızlık, söz vermeyi reddedeceğiniz bir şey midir?


Kimlik doğrulama başarısız olursa, sen gerektiğini rejectve yanlış döndürmez, ancak bir olmaya değer bekliyoruz Bool, o zaman vardı başarılı ve değeri ne olursa olsun Bool ile çözmelidir. Sözler, değerler için bir tür vekildir - döndürülen değeri saklarlar, bu nedenle, eğer değer siz elde edilemezse reject. Aksi takdirde yapmalısın resolve.

Bu iyi bir soru. Söz verilen tasarımın başarısızlıklarından birine değiniyor. İki tür hata vardır; bir kullanıcının hatalı giriş sağlaması (örneğin, giriş yapamama gibi) gibi beklenen hatalar ve kodda hata bulunan beklenmeyen hatalar. Söz verilen tasarım, iki kavramı tek bir akışta birleştirir ve bu ikisini kullanım için ayırt etmeyi zorlaştırır.
zzzzBov

1
Çözmenin , yanıtı kullanmak ve başvurunuzu sürdürmek anlamına geldiğini , reddetmek ise mevcut işlemi iptal etmek anlamına geldiğini (ve muhtemelen tekrar deneyin veya başka bir şey yapın) derim .

4
bunu düşünmenin başka bir yolu - bu senkronize bir yöntem çağrısı olsaydı, düzenli kimlik doğrulama başarısızlığına (hatalı kullanıcı adı / şifre) geri dönüyor mu falseyoksa istisna mı atıyorsun?
wrschneider

2
Getirme API buna iyi bir örnektir. Her zaman thensunucu yanıt verdiğinde tetiklenir - bir hata kodu döndürülse bile - ve bunu kontrol etmeniz gerekir response.ok. catchİşleyici yalnızca tetiklenir beklenmedik hatalar.
CodingIntrigue

Yanıtlar:


22

İyi soru! Zor bir cevap yok. Akışın o noktasında istisnai olarak düşündüğünüz şeylere bağlıdır .

A'yı reddetmek Promiseaynıdır, bir istisna yaratmaktır. İstenmeyen sonuçların tümü istisnai değil , hataların sonucudur . Davanızı iki yönden de tartışabilirsiniz:

  1. Başarısız kimlik doğrulama gerektiğini , arayan bir bekliyor çünkü karşılığında nesneyi ve başka bir şey bu akışı bir istisnadır.rejectPromiseUser

  2. Başarısız kimlik doğrulama gerektiğini , hiç de olsa yanlış kimlik bilgilerini sağlayarak gerçekten olmadığından, istisnai durum ve arayan her zaman akışı neden beklememelisiniz .resolvePromisenullUser

Unutmayın ki konuya arayanın tarafından bakıyorum . Bilgi akışında, arayan kişi eylemlerinin bir sonuç vermesini beklerUser (ve herhangi bir şey bir hatadır) veya bu belirli arayan kişinin diğer sonuçları ele alması mantıklı mıdır?

Çok katmanlı bir sistemde, veriler katmanlardan akarken cevap değişebilir. Örneğin:

  • HTTP katmanı RESOLVE! Diyor İstek gönderildi, yuva temiz bir şekilde kapatıldı ve sunucu geçerli bir yanıt verdi. Getirme API yapar.
  • Protokol katmanı daha sonra REJECT! Yanıttaki durum kodu 401 idi, bu HTTP için uygun, fakat protokol için değil!
  • Kimlik doğrulama katmanı HAYIR, RESOLVE diyor! 401 yanlış bir parola için beklenen durum olduğundan ve bir nullkullanıcıyı çözdüğü için hatayı yakalar .
  • Arabirim denetleyicisi BU YOK, diyor! Ekranda gösterilen modal bir kullanıcı adı ve bir avatar bekliyordu ve bu bilgiden başka bir şey bu noktada bir hatadır.

Bu 4-nokta örneği açıkça karmaşık, ancak 2 puan gösteriyor:

  1. Bir şeyin bir istisna / reddetme olup olmadığı, çevre akışına ve beklentilere bağlıdır
  2. Programınızın farklı katmanları, akışın farklı aşamalarında yer aldığı için aynı sonucu farklı şekilde ele alabilir

Yani yine, zor bir cevap yok. Düşünme ve tasarlama zamanı!


6

Dolayısıyla, Sözler, JS'yi fonksiyonel dillerden getirme konusunda güzel bir özelliğe sahiptir; bu nedenle , mantığı bir dalı ya da diğerini almaya zorlayarak, Eitherdiğer iki türü, Lefttürü ve türü bir araya getiren bu tür kurucuyu uygularlar. Rightdalı.

data Either x y = Left x | Right y

Şimdi sol taraftaki türün sözler için belirsiz olduğunu gerçekten fark ediyorsunuz; bir şeyle reddedebilirsin. Bu doğru, çünkü JS zayıf yazılmıştır, ancak defansif olarak programlama yapıyorsanız dikkatli olmak istersiniz .

Bunun nedeni, JS'nin throwsöz işleme kodundan ifadeler alıp bunu da bu Lefttarafa toplamasıdır. Teknik olarak JS'de throwdoğru / yanlış veya bir dize veya sayı da dahil olmak üzere her şeyi yapabilirsiniz : ancak JavaScript kodu da olmayan şeyleri atarthrow (boş değerlerde özelliklere erişmeye çalışmak gibi şeyler yaptığınızda) ve bunun için yerleşik bir API vardır ( Errornesne) . Bu yüzden yakalamak için etrafta dolaştığınızda, bu hataların Errornesne olduğunu varsaymak genellikle güzeldir . Ve rejectsöz için, yukarıdaki hataların herhangi birindeki herhangi bir hatada toplanacağından, ifadenizin basit ve tutarlı bir mantığı olmasını sağlamak için genellikle sadece throwdiğer hataları yapmak istersiniz catch.

Bu nedenle , içine şartlı bir koşul koyabilir catchve yanlış hataları arayabilseniz de , bu durumda gerçek durum önemsizdir,

Either (Either Error ()) ()

Muhtemelen mantık yapısını, en azından basit bir boolenin onaylayıcıdan hemen gelenler için tercih edersiniz :

Either Error Bool

Aslında, bir sonraki kimlik doğrulama mantığı seviyesi muhtemelen Userkimliği doğrulanmış kullanıcıyı içeren bir tür nesneyi döndürmektir ;

Either Error (Maybe User)

ve bu beklentilerimin az ya da çok olması: nullkullanıcının tanımlanmadığı durumda geri dön, yoksa geri dön {user_id: <number>, permission_to_launch_missiles: <boolean>}. Bence "yeni müşterilere demo" modunda çeşit iseniz giriş yapmış olmama genel durum örneğin, kurtarılabilir olduğunu beklediğiniz ve yanlışlıkla denilen böcek karışık olmamalıdır object.doStuff()zaman object.doStuffoldu undefined.

Şimdi bunu dedi, ne yapmak isteyebilirsiniz tanımlamaktır NotLoggedInya PermissionErrorgelen hangi türemiştir istisna Error. Sonra gerçekten ihtiyacınız olan şeylerde yazmak istiyorsunuz:

function launchMissiles() {
    function actuallyLaunchThem() {
        // stub
    }
    return getAuth().then(auth => {
        if (auth === null) {
            throw new PermissionError('Cannot launch missiles without permission, cannot have permission if not logged in.');
        } else if (auth.permission_to_launch_missiles) {
            return actuallyLaunchThem();
        } else {
            throw new PermissionError(`User ${auth.user_id} does not have permission to launch the missiles.`);
        }
    });
}

3

Hatalar

Hatalardan bahsedelim.

İki tür hata vardır:

  • beklenen hatalar
  • beklenmeyen hatalar
  • tek tek hatalar

Beklenen Hatalar

Beklenen hatalar, yanlış şeyin gerçekleştiği durumlardır ancak bunun olabileceğini biliyorsunuzdur, bu yüzden başa çıkacaksınız.

Bunlar kullanıcı girişi veya sunucu istekleri gibi şeylerdir. Kullanıcının bir hata yapabileceğini veya sunucunun kapalı olabileceğini biliyorsunuz, bu nedenle programın tekrar giriş yapmasını istediğinden veya bir mesaj görüntülediğinden veya uygun olan diğer davranışlardan emin olmak için bazı kontrol kodları yazıyorsunuz.

Bunlar, ele alındığında kurtarılabilir. Taşınırsa, beklenmeyen hatalar ortaya çıkar.

Beklenmeyen Hatalar

Beklenmeyen hatalar (hatalar), kod yanlış olduğu için yanlış şeyin gerçekleştiği durumlardır. Sonunda gerçekleşeceklerini biliyorsunuz, ancak onlarla nasıl veya nasıl başa çıkacaklarını bilmenin bir yolu yok, çünkü, tanımı gereği beklenmedikler.

Bunlar, sözdizimi ve mantık hataları gibi şeylerdir. Kodunuzda bir yazım hatası olabilir, yanlış parametrelerle bir işlev çağırmış olabilirsiniz. Bunlar tipik olarak kurtarılamaz.

try..catch

Hakkında konuşalım try..catch.

JavaScript’te throwyaygın olarak kullanılmaz. Kod içindeki örneklere bakarsanız, aralarında çok az ve çok olacak ve genellikle satırları boyunca yapılandırılmış olacaklardır.

function example(param) {
  if (!Array.isArray(param) {
    throw new TypeError('"param" should be an array!');
  }
  ...
}

Bu nedenle, try..catchbloklar da kontrol akışı için o kadar yaygın değildir. Beklenen hataları önlemek için yöntemleri çağırmadan önce bazı kontrolleri eklemek genellikle oldukça kolaydır.

JavaScript ortamları da oldukça affedilir, bu yüzden beklenmeyen hatalar çoğu zaman yakalanmamış kalır.

try..catchnadir olmak zorunda değildir. Java ve C # gibi dillerde daha yaygın olan bazı hoş kullanım durumları vardır. Java ve C #, yazılan catchyapıların avantajına sahiptir ; böylece beklenen ve beklenmeyen hatalar arasında ayrım yapabilirsiniz:

C # :
try
{
  var example = DoSomething();
}
catch (ExpectedException e)
{
  DoSomethingElse(e);
}

Bu örnek, diğer beklenmeyen istisnaların başka bir yere akmasını ve başka yerlerde kullanılmasını sağlar (örneğin, günlüğe kaydedilerek ve programdan kapatılarak).

JavaScript'te, bu yapı şununla kopyalanabilir:

try {
  let example = doSomething();
} catch (e) {
  if (e instanceOf ExpectedError) {
    DoSomethingElse(e);
  } else {
    throw e;
  }
}

Bu kadar zarif değil, bu da nadir olmasının nedeninin bir parçası.

Fonksiyonlar

Fonksiyonlar hakkında konuşalım.

Eğer kullanırsanız tek bir sorumluluk ilkesini , her sınıf ve işlev tekil bir amaca hizmet etmelidir.

Örneğin, authenticate()bir kullanıcının kimliğini doğrulayabilir.

Bu şöyle yazılabilir:

const user = authenticate();
if (user == null) {
  // keep doing stuff
} else {
  // handle expected error
}

Alternatif olarak şu şekilde yazılabilir:

try {
  const user = authenticate();
  // keep doing stuff
} catch (e) {
  if (e instanceOf AuthenticationError) {
    // handle expected error
  } else {
    throw e;
  }
}

Her ikisi de kabul edilebilir.

sözler

Hadi sözler hakkında konuşalım.

Sözler eşzamansız bir şeklidir try..catch. Kodunuzu arayın new Promiseveya Promise.resolvebaşlatır try. Aramak throwveya Promise.rejectsizi catchkoda gönderir .

Promise.resolve(value)   // try
  .then(doSomething)     // try
  .then(doSomethingElse) // try
  .catch(handleError)    // catch

Bir kullanıcının kimliğini doğrulamak için eşzamansız bir işleve sahipseniz, şu şekilde yazabilirsiniz:

authenticate()
  .then((user) => {
    if (user == null) {
      // keep doing stuff
    } else {
      // handle expected error
    }
  });

Alternatif olarak şu şekilde yazılabilir:

authenticate()
  .then((user) => {
    // keep doing stuff
  })
  .catch((e) => {
    if (e instanceOf AuthenticationError) {
      // handle expected error
    } else {
      throw e;
    }
  });

Her ikisi de kabul edilebilir.

yuvalama

Yuvalama hakkında konuşalım.

try..catchiç içe geçebilir. Kişisel authenticate()yöntem içten bir olabilir try..catchbloğu gibi:

try {
  const credentials = requestCredentialsFromUser();
  const user = getUserFromServer(credentials);
} catch (e) {
  if (e instanceOf CredentialsError) {
    // handle failure to request credentials
  } else if (e instanceOf ServerError) {
    // handle failure to get data from server
  } else {
    throw e; // no idea what happened
  }
}

Aynı şekilde sözler yuvalanabilir. Zaman uyumsuz authenticate()yönteminiz dahili olarak vaatlerde bulunabilir:

requestCredentialsFromUser()
  .then(getUserFromServer)
  .catch((e) => {
    if (e instanceOf CredentialsError) {
      // handle failure to request credentials
    } else if (e instanceOf ServerError) {
      // handle failure to get data from server
    } else {
      throw e; // no idea what happened
    }
  });

Peki cevap ne?

Tamam, sanırım şu soruya cevap vermenin zamanı geldi:

Kimlik doğrulamadaki bir başarısızlık söz vermeyi reddedeceğiniz bir şey midir?

Verebileceğim en basit cevap throw, senkronize kod olsaydı istisna yapmak istediğiniz herhangi bir yerde bir söz vermeyi reddetmeniz gerektiğidir .

Kontrol akışınız ifadelerinizde birkaç ifkontrol yaparak daha basitse then, söz vermeyi reddetmenize gerek yoktur.

Kontrol akışınız bir söz vermeyi reddederek ve ardından hata işleme kodunuzdaki hata türlerini kontrol ederek daha basitse, bunu yapın.


0

JQuery UI iletişim kutularının "iptal" eylemini temsil etmek için bir Sözün "reddet" dalını kullandım. "Çözüm" dalını kullanmaktan daha doğal gözüküyordu, en önemlisi, bir iletişim kutusunda genellikle birden fazla "yakın" seçeneğinin bulunmasıydı.


Tanıdığım çoğu temizlikçi seninle aynı fikirde değil.

0

Bir söz vermek, "if" koşulu gibi ya da azdır . Kimlik doğrulama başarısız olursa "çözmek" veya "reddetmek" isteyip istemediğiniz size bağlıdır.


1
söz bir asenkron try..catch, değil if.
zzzzBov

@zzzBox bu mantığa göre asenkron olarak bir Promise kullanmalı try...catchve basitçe tamamlayıp bir sonuç elde edebiliyorsanız, alınan değer ne olursa olsun çözmelisiniz, yoksa reddetmelisiniz?

Bir şey yok, hayır, tartışmamı yanlış anlamışsın. try { if (!doSomething()) throw whatever; doSomethingElse() } catch { ... }Tamamen gayet iyi, ama Promisetemsil ettiği yapı try..catchparça değil ifparçadır.
zzzzBov

@zzzzBov Bütün adalet içinde anladım :) Ben benzetmeyi seviyorum. Ama benim mantığım, basitçe doSomething()başarısız olursa, o zaman fırlatacak, ama eğer ihtiyacınız olmayan değeri içeremezse ( ifyukarıda sizin fikrinizin bir parçası olmadığı için biraz kafa karıştırıcıdır :)). Atmak için bir neden varsa (analojide), yani test başarısız olursa reddetmelisiniz. Test başarılı olursa, değerinin pozitif olup olmadığına bakmaksızın her zaman çözmelisiniz, değil mi?

Bir yerde, bir cevap yazmaya karar verdim (bunun yeterince açık kalacağını varsayarsak), çünkü yorumlar düşüncelerimi ifade etmek için yeterli değil.
zzzzBov
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.