Söz zincirinde birden fazla yakalama işlemek


125

Sözler konusunda hala oldukça yeniyim ve şu anda bluebird kullanıyorum, ancak bununla nasıl başa çıkacağımı tam olarak bilmediğim bir senaryom var.

Örneğin bir ekspres uygulamada şöyle bir söz zincirim var:

repository.Query(getAccountByIdQuery)
        .catch(function(error){
            res.status(404).send({ error: "No account found with this Id" });
        })
        .then(convertDocumentToModel)
        .then(verifyOldPassword)
        .catch(function(error) {
            res.status(406).send({ OldPassword: error });
        })
        .then(changePassword)
        .then(function(){
            res.status(200).send();
        })
        .catch(function(error){
            console.log(error);
            res.status(500).send({ error: "Unable to change password" });
        });

Yani peşinde olduğum davranış:

  • Kimliğe göre hesap almaya gider
  • Bu noktada bir ret olursa, bombayı atın ve bir hata verin
  • Hata yoksa, iade edilen belgeyi modele dönüştürün
  • Şifreyi veritabanı belgesiyle doğrulayın
  • Parolalar uyuşmuyorsa bombalayın ve farklı bir hata verin
  • Hata yoksa şifreleri değiştirin
  • Sonra başarıya dön
  • Başka bir şey ters giderse, 500 iade edin

Yani şu anda yakalananların zincirlemeyi durdurmadığı görülüyor ve bu mantıklı, bu yüzden zinciri bir şekilde hatalara bağlı olarak belirli bir noktada durmaya zorlamanın bir yolu olup olmadığını veya daha iyi bir yol olup olmadığını merak ediyorum. bunu bir çeşit dallanma davranışı elde edecek şekilde yapılandırmak için if X do Y else Z.

Herhangi bir yardım harika olur.


Ya yeniden atabilir misin yoksa erken dönebilir misin?
Pieter21

Yanıtlar:


126

Bu davranış tam olarak senkronize bir atış gibidir:

try{
    throw new Error();
} catch(e){
    // handle
} 
// this code will run, since you recovered from the error!

Bu .catch, hatalardan kurtulmanın amacının yarısıdır . Durumun hala bir hata olduğunu belirtmek için yeniden atılması istenebilir:

try{
    throw new Error();
} catch(e){
    // handle
    throw e; // or a wrapper over e so we know it wasn't handled
} 
// this code will not run

Ancak, hata daha sonraki bir işleyici tarafından yakalandığından, bu tek başına sizin durumunuzda işe yaramayacaktır. Buradaki asıl sorun, genelleştirilmiş "HER ŞEYİ ELLE" hata işleyicilerinin genel olarak kötü bir uygulama olması ve diğer programlama dillerinde ve ekosistemlerde aşırı derecede hoş karşılanmamasıdır. Bu nedenle Bluebird, yazılı ve yüklemli yakalamalar sunar.

Ek avantaj, iş mantığınızın istek / yanıt döngüsünün hiç farkında olmaması (ve olmaması) gerekmesidir. İstemcinin hangi HTTP durumunu ve hatasını alacağına karar vermek sorgunun sorumluluğu değildir ve daha sonra uygulamanız büyüdükçe iş mantığını (DB'nizi nasıl sorgulayacağınız ve verilerinizi nasıl işleyeceğiniz) müşteriye gönderdiğiniz şeyden ayırmak isteyebilirsiniz. (hangi http durum kodu, hangi metin ve hangi yanıt).

İşte kodunuzu nasıl yazacağım.

İlk olarak, Bluebird'ün zaten sağladığı bir alt sınıfa .Queryatacağım . Bir hatayı nasıl alt sınıfa ayıracağınızdan emin değilseniz bana bildirin.NoSuchAccountErrorPromise.OperationalError

Ek olarak alt sınıfa ayırır AuthenticationErrorve sonra şöyle bir şey yaparım:

function changePassword(queryDataEtc){ 
    return repository.Query(getAccountByIdQuery)
                     .then(convertDocumentToModel)
                     .then(verifyOldPassword)
                     .then(changePassword);
}

Görebileceğiniz gibi - çok temiz ve metni süreçte ne olduğuna dair bir talimat kılavuzu gibi okuyabilirsiniz. Ayrıca istek / yanıttan da ayrılmıştır.

Şimdi, rota işleyicisinden şöyle çağırırım:

 changePassword(params)
 .catch(NoSuchAccountError, function(e){
     res.status(404).send({ error: "No account found with this Id" });
 }).catch(AuthenticationError, function(e){
     res.status(406).send({ OldPassword: error });
 }).error(function(e){ // catches any remaining operational errors
     res.status(500).send({ error: "Unable to change password" });
 }).catch(function(e){
     res.status(500).send({ error: "Unknown internal server error" });
 });

Bu şekilde, mantık tek bir yerde ve müşteriye hataların nasıl ele alınacağına dair karar tek bir yerde ve birbirlerini karıştırmıyorlar.


11
.catch(someSpecificError)Belirli bir hata için bir ara işleyiciye sahip olmanın nedeninin, belirli bir hata türünü (yani zararsız) yakalamak, onunla ilgilenmek ve takip eden akışa devam etmek istiyorsanız olduğunu eklemek isteyebilirsiniz. Örneğin, yapacak bir dizi iş içeren bazı başlangıç ​​kodum var. İlk şey, yapılandırma dosyasını diskten okumaktır, ancak bu yapılandırma dosyası eksikse, bu bir OK hatasıdır (programın varsayılanları vardır), böylece bu belirli hatayı halledebilir ve akışın geri kalanına devam edebilirim. Daha sonrasına kadar ayrılmamak için daha iyi bir temizlik olabilir.
jfriend00

1
"Bu .catch'in anlamının yarısı - hatalardan kurtulabilmek" fikrinin bunu netleştirdiğini düşündüm, ancak daha fazla açıklığa kavuşturduğunuz için teşekkürler, bu iyi bir örnek.
Benjamin Gruenbaum

1
Ya mavi kuş kullanılmıyorsa? Düz es6 vaatleri yalnızca catch'e aktarılan bir dize hata mesajına sahiptir.
saat ustası

3
ES6 ile @clocksmith, her şeyi yakalamaya ve instanceofkendi başınıza chceks yapmaya zorlandığınızı vaat ediyor .
Benjamin Gruenbaum

1
Error nesnelerinin alt sınıflamasına yönelik bir referans arayanlar için bluebirdjs.com/docs/api/catch.html#filtered-catch adresini okuyun . Makale ayrıca burada verilen çoklu yakalama yanıtını hemen hemen yeniden üretmektedir.
mummybot

47

.catchtry-catchifade gibi çalışır , yani sonunda yalnızca bir yakalamaya ihtiyacınız vardır:

repository.Query(getAccountByIdQuery)
        .then(convertDocumentToModel)
        .then(verifyOldPassword)
        .then(changePassword)
        .then(function(){
            res.status(200).send();
        })
        .catch(function(error) {
            if (/*see if error is not found error*/) {
                res.status(404).send({ error: "No account found with this Id" });
            } else if (/*see if error is verification error*/) {
                res.status(406).send({ OldPassword: error });
            } else {
                console.log(error);
                res.status(500).send({ error: "Unable to change password" });
            }
        });

1
Evet bunu biliyordum ama büyük bir hata zinciri yapmak istemiyordum ve ihtiyaç duyduğu zaman ve zamanda yapmak daha okunaklı görünüyordu. Bu nedenle, sonunda her şeyi yakalar, ancak niyet açısından daha açıklayıcı olduğu için yazılan hatalar fikrini seviyorum.
Grofit

8
@Grofit değeri ne olursa olsun - Bluebird'de yazılan yakalamalar , Petka (Esailija) 'nın fikriydi :) Onu burada tercih edilen bir yaklaşım olduklarına ikna etmeye gerek yok. JS'deki pek çok insan bu kavramın pek farkında olmadığı için kafanızı karıştırmak istemediğini düşünüyorum.
Benjamin Gruenbaum

17

Hatalara dayanarak zinciri bir şekilde belirli bir noktada durmaya zorlamanın bir yolu olup olmadığını merak ediyorum.

Hayır. Sonuna kadar kabarcıklar oluşturan bir istisna oluşturmadıkça, bir zinciri gerçekten "sonlandıramazsınız". Bunun nasıl yapılacağı konusunda Benjamin Gruenbaum'un cevabına bakın .

Onun modelinin türetilmesi, hata türlerini ayırt etmek değil, tek bir genel işleyiciden gönderilebilen alanları statusCodeve sahip bodyolan hataları kullanmak olacaktır .catch. Uygulama yapınıza bağlı olarak, çözümü daha temiz olabilir.

veya bunu bir çeşit dallanma davranışı elde etmek için yapılandırmanın daha iyi bir yolu varsa

Evet, vaatlerle dallanma yapabilirsiniz . Bununla birlikte, bu, zinciri terk etmek ve iç içe yerleştirmeye "geri dönmek" anlamına gelir - tıpkı eğer varsa-değilse veya dene-yakala ifadesinde yaptığınız gibi:

repository.Query(getAccountByIdQuery)
.then(function(account) {
    return convertDocumentToModel(account)
    .then(verifyOldPassword)
    .then(function(verification) {
        return changePassword(verification)
        .then(function() {
            res.status(200).send();
        })
    }, function(verificationError) {
        res.status(406).send({ OldPassword: error });
    })
}, function(accountError){
    res.status(404).send({ error: "No account found with this Id" });
})
.catch(function(error){
    console.log(error);
    res.status(500).send({ error: "Unable to change password" });
});

5

Ben şu şekilde yapıyorum:

Sonunda avını bırak. Ve zincirinizin ortasında meydana geldiğinde bir hata verin.

    repository.Query(getAccountByIdQuery)
    .then((resultOfQuery) => convertDocumentToModel(resultOfQuery)) //inside convertDocumentToModel() you check for empty and then throw new Error('no_account')
    .then((model) => verifyOldPassword(model)) //inside convertDocumentToModel() you check for empty and then throw new Error('no_account')        
    .then(changePassword)
    .then(function(){
        res.status(200).send();
    })
    .catch((error) => {
    if (error.name === 'no_account'){
        res.status(404).send({ error: "No account found with this Id" });

    } else  if (error.name === 'wrong_old_password'){
        res.status(406).send({ OldPassword: error });

    } else {
         res.status(500).send({ error: "Unable to change password" });

    }
});

Diğer işlevleriniz muhtemelen şuna benzer:

function convertDocumentToModel(resultOfQuery) {
    if (!resultOfQuery){
        throw new Error('no_account');
    } else {
    return new Promise(function(resolve) {
        //do stuff then resolve
        resolve(model);
    }                       
}

4

Muhtemelen partiye biraz geç kalmıştır, ancak .catchburada gösterildiği gibi yuva yapmak mümkündür :

Mozilla Geliştirici Ağı - Sözleri Kullanma

Düzenleme: Bunu, genel olarak istenen işlevselliği sağladığı için gönderdim. Ancak bu özel durumda değil. Çünkü zaten başkaları tarafından ayrıntılı olarak açıklandığı gibi .catch, hatayı düzeltmesi gerekiyor. Sen, örneğin, müşteriye bir yanıt gönderemez birden .catch bir nedeni geri aramaları .catchhiçbir açık olan return giderir ile undefinedbu durumda, davayı neden .thenZincir gerçekten çözülmezse rağmen tetiğe, potansiyel olarak aşağıdaki neden .catchtetik ve gönderme için Müşteriye başka bir yanıt, bir hataya neden olur ve muhtemelen UnhandledPromiseRejectionsizin yolunuza düşer . Umarım bu karmaşık cümle size bir anlam ifade etmiştir.


1
@AntonMenshov Haklısın. Cevabımı genişlettim, istediği davranışın neden yuvalama ile hala mümkün olmadığını
açıkladım

2

.then().catch()...Senin yerine yapabilirsin .then(resolveFunc, rejectFunc). Bu vaat zinciri, işlerin üstesinden gelirseniz daha iyi olur. İşte onu nasıl yeniden yazacağım:

repository.Query(getAccountByIdQuery)
    .then(
        convertDocumentToModel,
        () => {
            res.status(404).send({ error: "No account found with this Id" });
            return Promise.reject(null)
        }
    )
    .then(
        verifyOldPassword,
        () => Promise.reject(null)
    )
    .then(
        changePassword,
        (error) => {
            if (error != null) {
                res.status(406).send({ OldPassword: error });
            }
            return Promise.Promise.reject(null);
        }
    )
    .then(
        _ => res.status(200).send(),
        error => {
            if (error != null) {
                console.error(error);
                res.status(500).send({ error: "Unable to change password" });
            }
        }
    );

Not: Bu if (error != null), en son hatayla etkileşime girmek için biraz zor.


1

Benjamin Gruenbaum'un yukarıdaki cevabının karmaşık bir mantık dizisi için en iyi çözüm olduğunu düşünüyorum , ancak işte daha basit durumlar için alternatifim. Sonraki veya ifadeleri atlamak için errorEncounteredbirlikte bir bayrak kullanıyorum . Yani şöyle görünecektir:return Promise.reject()thencatch

let errorEncountered = false;
someCall({
  /* do stuff */
})
.catch({
  /* handle error from someCall*/
  errorEncountered = true;
  return Promise.reject();
})
.then({
  /* do other stuff */
  /* this is skipped if the preceding catch was triggered, due to Promise.reject */
})
.catch({
  if (errorEncountered) {
    return;
  }
  /* handle error from preceding then, if it was executed */
  /* if the preceding catch was executed, this is skipped due to the errorEncountered flag */
});

İkiden fazla o zaman / catch çiftiniz varsa, muhtemelen Benjamin Gruenbaum'un çözümünü kullanmalısınız. Ancak bu, basit bir kurulum için işe yarar.

Finalin catchsadece return;yerine sahip olduğunu unutmayın return Promise.reject();, çünkü atlamamız gereken başka bir şey yoktur thenve bu, Node'un hoşlanmadığı, işlenmemiş bir Söz reddi olarak sayılır. Yukarıda yazıldığı gibi, final catchbarışçıl bir şekilde çözülmüş bir Söz verecektir.

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.