Neden bir Promise.catch işleyicisinin içine giremiyorum?


127

Neden Errorcatch geri aramasının içinden bir geri arama atıp sürecin hatayı başka bir kapsamdaymış gibi işlemesine izin veremiyorum ?

Hiçbir şey yapmazsam console.log(err)yazdırılır ve ne olduğu hakkında hiçbir şey bilmiyorum. İşlem bitiyor ...

Misal:

function do1() {
    return new Promise(function(resolve, reject) {
        throw new Error('do1');
        setTimeout(resolve, 1000)
    });
}

function do2() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            reject(new Error('do2'));
        }, 1000)
    });
}

do1().then(do2).catch(function(err) {
    //console.log(err.stack); // This is the only way to see the stack
    throw err; // This does nothing
});

Geri çağırmalar ana iş parçacığında yürütülürse, neden Errorbir kara delik tarafından yutulur?


11
Bir kara delik tarafından yutulmaz. Geri .catch(…)dönen sözü reddeder .
Bergi


yerine .catch((e) => { throw new Error() }), yazın .catch((e) => { return Promise.reject(new Error()) })veya basitçe.catch((e) => Promise.reject(new Error()))
chharvey

1
@chharvey, yorumunuzdaki tüm kod parçacıkları, tam olarak aynı davranışa sahiptir, ancak ilk başta açıkça en açık olanıdır.
Каверей Гринько

Yanıtlar:


157

Başkalarının da açıkladığı gibi, "kara delik", bir .catchzincirin içine atmanın reddedilmiş bir sözle devam etmesi ve daha fazla yakalanmanızın kalmaması, sonlandırılmamış bir zincire yol açması ve bu da hataları yutmasıdır (kötü!)

Neler olduğunu görmek için bir yakalama daha ekleyin:

do1().then(do2).catch(function(err) {
    //console.log(err.stack); // This is the only way to see the stack
    throw err; // Where does this go?
}).catch(function(err) {
    console.log(err.stack); // It goes here!
});

Zincirin ortasındaki bir yakalama, zincirin başarısız bir adıma rağmen ilerlemesini istediğinizde kullanışlıdır, ancak bilgileri günlüğe kaydetme veya temizleme adımları gibi şeyleri yaptıktan sonra, hatta belki de hangi hatayı değiştirerek, başarısız olmaya devam etmek için yeniden atma yararlıdır. Atıldı.

Hile

Hatanın web konsolunda bir hata olarak görünmesini sağlamak için, başlangıçta amaçladığınız gibi, şu numarayı kullanıyorum:

.catch(function(err) { setTimeout(function() { throw err; }); });

Satır numaraları bile hayatta kalır, bu nedenle web konsolundaki bağlantı beni doğrudan (orijinal) hatanın meydana geldiği dosyaya ve satıra götürür.

Neden işe yarıyor

Sözün yerine getirilmesi veya reddedilme işleyicisi olarak adlandırılan bir işlevdeki herhangi bir istisna, otomatik olarak iade etmeniz gereken sözün reddine dönüştürülür. Fonksiyonunuzu çağıran vaat kodu bununla ilgilenir.

Öte yandan setTimeout tarafından çağrılan bir işlev, her zaman JavaScript kararlı durumundan çalışır, yani JavaScript olay döngüsünde yeni bir döngüde çalışır. İstisnalar hiçbir şey tarafından yakalanmaz ve bunu web konsoluna aktarır. Yana errözgün yığını, dosya ve satır numarası dahil olmak hatayla ilgili tüm bilgileri, tutar, hala doğru raporlanır.


3
Jib, bu ilginç bir numara, neden işe yaradığını anlamama yardım edebilir misin?
Brian Keith

8
Bu numara hakkında: kayıt yapmak istediğiniz için atıyorsunuz, öyleyse neden doğrudan giriş yapmıyorsunuz? Bu numara 'rastgele' bir zamanda yakalanamaz bir hata fırlatır .... Ancak istisnalar fikri (ve sözlerin bunlarla başa çıkma şekli) onu arayanın hatayı yakalama ve onunla başa çıkma sorumluluğu haline getirmektir. Bu kod, arayanın hatalarla ilgilenmesini etkili bir şekilde imkansız hale getirir. Neden bunu sizin için halledecek bir işlev yapmıyorsunuz? function logErrors(e){console.error(e)}o zaman gibi kullanın do1().then(do2).catch(logErrors). Yanıtın kendisi harika btw, +1
Stijn de Witt

3
@jib Bu durumda olduğu gibi aşağı yukarı bağlantılı birçok vaat içeren bir AWS lambda yazıyorum. Hata durumunda AWS alarmlarından ve bildirimlerinden yararlanmak için lambda çökmesini bir hata atarak yapmam gerekiyor (sanırım). Bunu elde etmenin tek yolu numara mı?
masciugo

2
@StijndeWitt Benim durumumda, window.onerrorolay işleyicide sunucuma hata ayrıntılarını göndermeye çalışıyordum . Bu sadece setTimeouthile yaparak yapılabilir. Aksi takdirde window.onerrorPromise'da yaşanan hatalarla ilgili hiçbir şey duymayacaksınız.
hudidit

1
@hudidit Yine, bu kadar wheter console.logya postErrorToServer, sadece yapılması gereken ne yapabiliriz. Kod ne olursa olsun, window.onerrorayrı bir işleve ayrılıp 2 yerden çağrılmaması için hiçbir sebep yoktur . Muhtemelen setTimeoutçizgiden daha kısadır .
Stijn de Witt

46

Burada anlaşılması gereken önemli şeyler

  1. Hem thenve catchişlevleri yeni vaadi nesneleri döndürür.

  2. Ya fırlatmak ya da açıkça reddetmek, mevcut sözü reddedilmiş duruma getirecektir.

  3. Yana thenve catchyeni vaadi nesneleri döndürmek, bunlar zincirleme edilebilir.

  4. Bir vaat işleyicisinin ( thenveya catch) içine atarsanız veya reddederseniz , zincirleme yolundaki bir sonraki ret işleyicisinde ele alınır.

  5. Jfriend00 tarafından belirtildiği gibi, thenve catchişleyicileri eşzamanlı olarak çalıştırılmaz. Bir eylemci fırlattığında, hemen sona erecektir. Böylece, yığın çözülecek ve istisna kaybedilecektir. Bu nedenle bir istisna atmak mevcut vaadi reddeder.


Sizin durumunuzda, do1bir Errornesne fırlatarak içeriyi reddediyorsunuz . Şimdi, mevcut söz reddedilmiş durumda olacak ve kontrol thenbizim durumumuzda olan bir sonraki işleyiciye aktarılacaktır .

Yana thenişleyici bir ret işleyicisi yok, do2hiç yürütülmez. Bunu console.logiçini kullanarak onaylayabilirsiniz . Mevcut sözün bir ret işleyicisi olmadığı için, önceki sözden gelen ret değeri ile de reddedilecek ve kontrol sonraki işleyiciye aktarılacaktır catch.

catchBir reddetme işleyicisi olduğu gibi , console.log(err.stack);içinde yaptığınız zaman, hata yığını izini görebilirsiniz. Şimdi, Errorondan bir nesne fırlatıyorsun, böylece geri dönen söz catchde reddedilmiş durumda olacak.

Adresine herhangi bir ret işleyicisi catcheklemediğiniz için reddi gözlemleyemezsiniz.


Zinciri bölebilir ve bunu daha iyi anlayabilirsiniz, bunun gibi

var promise = do1().then(do2);

var promise1 = promise.catch(function (err) {
    console.log("Promise", promise);
    throw err;
});

promise1.catch(function (err) {
    console.log("Promise1", promise1);
});

Elde edeceğiniz çıktı şu şekilde olacaktır:

Promise Promise { <rejected> [Error: do1] }
Promise1 Promise { <rejected> [Error: do1] }

İçinde catchişleyici 1, sen değerini alıyorsanız promisereddedilen olarak nesne.

Aynı şekilde, catchişleyici 1 tarafından döndürülen söz de reddedildiği aynı hatayla promisereddedilir ve biz bunu ikinci catchişleyicide gözlemliyoruz .


3
İşleyicilerin .then()eşzamansız olduğunu (yığın yürütülmeden önce çözüldüğünü) eklemeye değer olabilir , bu nedenle içlerindeki istisnaların reddedilmeye dönüştürülmesi gerekir, aksi takdirde onları yakalayacak istisna işleyiciler olmayacaktır.
jfriend00

7

setTimeout()Yukarıda detayları verilen yöntemi denedim ...

.catch(function(err) { setTimeout(function() { throw err; }); });

Can sıkıcı bir şekilde, bunu tamamen test edilemez buldum. Zaman uyumsuz bir hata attığı için, onu bir try/catchifadenin içine saramazsınız, çünkü catchzaman hatası atıldığında, dinlemeyi durdurmuş olacaktır.

Mükemmel çalışan bir dinleyici kullanmaya geri döndüm ve JavaScript'in kullanılması gerektiği için oldukça test edilebilirdi.

return new Promise((resolve, reject) => {
    reject("err");
}).catch(err => {
    this.emit("uncaughtException", err);

    /* Throw so the promise is still rejected for testing */
    throw err;
});

3
Jest'in bu durumu halletmesi gereken zamanlayıcı taklidi vardır.
jordanbtucker

2

Spesifikasyona göre (bkz 3.III.d) :

d. Çağırma daha sonra bir istisna atarsa, e,
  a. SolutionPromise veya rejectPromise çağrıldıysa, dikkate almayın.
  b. Aksi takdirde, sözü e ile reddedin.

Bu, thenişlevde istisna atarsanız , yakalanacağı ve sözünüzün reddedileceği anlamına gelir. catchburada bir anlam ifade etmiyor, bu sadece kısayol.then(null, function() {})

Sanırım kodunuza işlenmemiş retleri kaydetmek istiyorsunuz. Çoğu vaat, kütüphaneler için bir ateş unhandledRejectioneder. İşte bununla ilgili tartışmayla ilgili öz .


unhandledRejectionKancanın sunucu tarafı JavaScript için olduğunu, istemci tarafında farklı tarayıcıların farklı çözümleri olduğunu belirtmekte fayda var. Henüz standardize etmedik ama oraya yavaş ama emin adımlarla varıyor.
Benjamin Gruenbaum

1

Bunun biraz geç olduğunu biliyorum, ancak bu konuya rastladım ve çözümlerin hiçbiri benim için uygulanması kolay değildi, bu yüzden kendi başıma geldim:

Bir söz veren küçük bir yardımcı işlev ekledim, şöyle:

function throw_promise_error (error) {
 return new Promise(function (resolve, reject){
  reject(error)
 })
}

Ardından, söz zincirimden herhangi birinde bir hata atmak istediğim (ve sözü reddettiğim) belirli bir yerim varsa, yukarıdaki işlevden inşa edilmiş hatamla şöyle dönüyorum:

}).then(function (input) {
 if (input === null) {
  let err = {code: 400, reason: 'input provided is null'}
  return throw_promise_error(err)
 } else {
  return noterrorpromise...
 }
}).then(...).catch(function (error) {
 res.status(error.code).send(error.reason);
})

Bu şekilde, söz zincirinin içinden fazladan hatalar atma kontrolü bende. Aynı zamanda 'normal' vaat hatalarını da ele almak istiyorsanız, 'kendi kendine atılan' hataları ayrı ayrı ele almak için yakalamanızı genişletirsiniz.

Umarım bu yardımcı olur, bu benim ilk yığın akışı cevabımdır!


Promise.reject(error)bunun yerine new Promise(function (resolve, reject){ reject(error) })(zaten bir dönüş ifadesine ihtiyaç duyar)
Funkodebat

0

Evet, hataları yutma sözü .catchverir ve diğer yanıtlarda daha ayrıntılı olarak açıklandığı gibi, bunları yalnızca ile yakalayabilirsiniz . Node.js içindeyseniz ve normal throwdavranışı yeniden oluşturmak istiyorsanız , yığın izlemesini konsola yazdırıp çıkış işlemini yapabilirsiniz.

...
  throw new Error('My error message');
})
.catch(function (err) {
  console.error(err.stack);
  process.exit(0);
});

1
Hayır, sahip olduğunuz her söz zincirinin sonuna bunu koymanız gerekeceği için bu yeterli değil . Aksine üzerinde kanca unhandledRejectionolay
Bergi

Evet, sözlerinizi zincirlediğiniz varsayılır, böylece çıkış son işlevdir ve sonradan yakalanmaz. Bahsettiğiniz olay, bence sadece Bluebird kullanılıyorsa.
Jesús Carrera

Bluebird, Q, ne zaman, yerli vaatler,… Muhtemelen bir standart olacak.
Bergi
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.