JavaScript Vaatleri - reddetme ve atma


384

Bu konuda birkaç makale okudum, ancak Promise.rejecthata atma ile hata atma arasında bir fark olup olmadığı hala net değil . Örneğin,

Promise.reject kullanma

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            return Promise.reject(new PermissionDenied());
        }
    });

Fırlatma kullanma

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            throw new PermissionDenied();
        }
    });

Benim tercihim throwsadece daha kısa olduğu için kullanmak , ancak birinin diğerine göre herhangi bir avantajı olup olmadığını merak ediyordu.


9
Her iki yöntem de tam olarak aynı yanıtı üretir. .then()İşleyicisi istisnayı yakalar ve otomatik olarak bir reddedilen vaadi haline döner. Atılan istisnaların yürütülmesinin özellikle hızlı olmadığını okuduğumdan, reddedilen sözü geri vermenin yürütmenin biraz daha hızlı olabileceğini tahmin ediyorum, ancak eğer bilmek önemliyse, birden fazla modern tarayıcıda bir test tasarlamanız gerekirdi. Ben şahsen kullanıyorum throwçünkü okunabilirliği seviyorum.
jfriend00

@webduvet Promises ile değil - atış ile çalışmak üzere tasarlanmıştır.
joews

15
Bunun bir dezavantajı throw, setTimeout gibi eşzamansız bir geri arama içinden atılması durumunda reddedilen bir vaatle sonuçlanmamasıdır. jsfiddle.net/m07van33 @Blondie cevabınız doğru.
Kevin B

@joews iyi olduğu anlamına gelmez;)
webduvet

1
Ah, doğru. Benim yorum bir açıklama olurdu Yani, "eğer bir asenkron callback'inde içinden atıldı promisified değildi " . Bunun bir istisnası olduğunu biliyordum, ne olduğunu hatırlayamıyordum. Ben de atmayı kullanmayı tercih ediyorum çünkü daha okunabilir rejectolduğunu düşünüyorum ve param listemden çıkarmama izin veriyor .
Kevin B

Yanıtlar:


344

Birini diğerine karşı kullanmanın bir avantajı yoktur, ancak throwişe yaramayacağı özel bir durum vardır . Ancak, bu durumlar düzeltilebilir.

Ne zaman bir vaat geri arama içinde, kullanabilirsiniz throw. Ancak, başka bir eşzamansız geri arama yapıyorsanız, kullanmanız gerekir reject.

Örneğin, bu yakalamayı tetiklemez:

new Promise(function() {
  setTimeout(function() {
    throw 'or nah';
    // return Promise.reject('or nah'); also won't work
  }, 1000);
}).catch(function(e) {
  console.log(e); // doesn't happen
});

Bunun yerine, çözülmemiş bir vaat ve yakalanmamış bir istisna kaldı. Bu, bunun yerine kullanmak istediğiniz bir durumdur reject. Ancak, bunu iki şekilde düzeltebilirsiniz.

  1. zaman aşımı içindeki orijinal Promise'ın reddetme işlevini kullanarak:

new Promise(function(resolve, reject) {
  setTimeout(function() {
    reject('or nah');
  }, 1000);
}).catch(function(e) {
  console.log(e); // works!
});

  1. zaman aşımını vaat ederek:

function timeout(duration) { // Thanks joews
  return new Promise(function(resolve) {
    setTimeout(resolve, duration);
  });
}

timeout(1000).then(function() {
  throw 'worky!';
  // return Promise.reject('worky'); also works
}).catch(function(e) {
  console.log(e); // 'worky!'
});


54
Kullanamayacağınız vaat edilmemiş bir zaman uyumsuz geri arama içindeki yerlerin , OP'nin karşılaştırmamızı istediği şey de throw errorkullanamazsınız return Promise.reject(err). Temelde bu yüzden vaatlerin içine asenkron geri çağrılar koymamalısınız. Zaman uyumsuz olan her şeyi vaat edin ve sonra bu kısıtlamalara sahip değilsiniz.
jfriend00

9
"Ancak, başka bir tür geri arama yapıyorsanız" gerçekten "Ancak, başka tür bir eşzamansız geri arama yapıyorsanız " olmalıdır. Geri aramalar eşzamanlı (örn. İle) olabilir Array#forEachve bunlarla içine atmak işe yarayacaktır.
Félix Saparelli

2
@KevinB bu satırları okurken "atışın işe yaramayacağı özel bir durum var." ve "Söz verilen bir geri çağrının içindeyseniz, throw'ı kullanabilirsiniz. Ancak, başka bir eşzamansız geri çağırma içindeyseniz reddetme özelliğini kullanmalısınız." Örnek snippet'lerin throwişe yaramayacağı durumları göstereceğini ve bunun yerine Promise.rejectdaha iyi bir seçim olduğunu hissediyorum . Ancak snippet'ler bu iki seçenekten etkilenmez ve ne seçerseniz seçin aynı sonucu verir. Bir şey mi kaçırıyorum?
Anshul

2
Evet. setTimeout'ta throw kullanırsanız, catch çağrılmaz. geri aramaya rejectiletileni kullanmanız gerekir new Promise(fn).
Kevin B

2
@KevinB Birlikte kaldığınız için teşekkürler. OP tarafından verilen örnekte özellikle karşılaştırmak istediği belirtilmiştir return Promise.reject()ve throw. rejectYapıda verilen geri çağrıdan bahsetmez new Promise(function(resolve, reject)). İki parçacığınız, geri arama çözümünü ne zaman kullanmanız gerektiğini doğru bir şekilde gösterirken, OP'nin sorusu bu değildi.
Anshul

201

Bir diğer önemli gerçektir ki reject() YAPAR DEĞİL bir benzeri kontrol akışını sonlandırmak returndeyimi yapar. Aksine throwkontrol akışını sonlandırır.

Misal:

new Promise((resolve, reject) => {
  throw "err";
  console.log("NEVER REACHED");
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));

vs

new Promise((resolve, reject) => {
  reject(); // resolve() behaves similarly
  console.log("ALWAYS REACHED"); // "REJECTED" will print AFTER this
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));


51
Mesele doğru ama karşılaştırma zor. Çünkü normalde reddedilen sözünüzü yazarak iade etmelisiniz return reject(), böylece bir sonraki satır çalışmayacaktır.
AZ.

7
Neden iade etmek istersiniz?
lukyer

31
Bu durumda, return reject()basitçe kısaca, reject(); returnyani istediğinizi akışı sonlandırmaktır. Yürütücünün dönüş değeri (iletilen işlev new Promise) kullanılmaz, bu nedenle güvenlidir.
Félix Saparelli

47

Evet, en büyük fark, reddetmenin , vaat reddedildikten sonra gerçekleştirilen bir geri arama işlevidir, oysa atış asenkron olarak kullanılamaz. Reddetmeyi seçerseniz, kodunuz eşzamansız olarak normal şekilde çalışmaya devam ederken, atma çözücü işlevini tamamlamaya öncelik verir (bu işlev hemen çalışır).

Sorunun benim için netleştirilmesine yardımcı olduğunu gördüğüm bir örnek, reddetme ile bir Zaman Aşımı işlevi ayarlayabileceğinizdi, örneğin:

new Promise(_, reject) {
 setTimeout(reject, 3000);
});

Yukarıdakilerin fırlatmayla yazmak mümkün olamazdı.

Küçük örneğinizde ayırt edilemeyen fark, ancak daha karmaşık asenkron kavramla uğraşırken ikisi arasındaki fark büyük olabilir.


1
Bu kilit bir kavram gibi geliyor, ama yazılı olarak anlamıyorum. Sözler için hala çok yeni sanırım.
David Spector

43

TLDR: Bir fonksiyon bazen bir söz verdiğinde ve bazen bir istisna attığında kullanmak zordur. Bir zaman uyumsuzluk işlevi yazarken, reddedilen bir vaat döndürerek hataya işaret etmeyi tercih edin

Özel örneğiniz, aralarındaki bazı önemli ayrımları gizler:

Bir söz zinciri içinde hata işlediğiniz için , atılan istisnalar otomatik olarak reddedilen sözlere dönüştürülür . Bu neden değiştirilebilir olduklarını açıklayabilir - değildir.

Aşağıdaki durumu düşünün:

checkCredentials = () => {
    let idToken = localStorage.getItem('some token');
    if ( idToken ) {
      return fetch(`https://someValidateEndpoint`, {
        headers: {
          Authorization: `Bearer ${idToken}`
        }
      })
    } else {
      throw new Error('No Token Found In Local Storage')
    }
  }

Bu bir anti-desen olacaktır, çünkü daha sonra hem asenkron hem de senkronizasyon hata vakalarını desteklemeniz gerekir. Şuna benzeyebilir:

try {
  function onFulfilled() { ... do the rest of your logic }
  function onRejected() { // handle async failure - like network timeout }
  checkCredentials(x).then(onFulfilled, onRejected);
} catch (e) {
  // Error('No Token Found In Local Storage')
  // handle synchronous failure
} 

İyi değil ve işte tam olarak Promise.reject(küresel kapsamda mevcut) kurtarmaya nereden geliyor ve kendini etkili bir şekilde farklı kılıyor throw. Refactor şimdi:

checkCredentials = () => {
  let idToken = localStorage.getItem('some_token');
  if (!idToken) {
    return Promise.reject('No Token Found In Local Storage')
  }
  return fetch(`https://someValidateEndpoint`, {
    headers: {
      Authorization: `Bearer ${idToken}`
    }
  })
}

Bu artık catch()ağ arızaları ve jeton eksikliği için senkron hata kontrolü için sadece bir tane kullanmanızı sağlar :

checkCredentials()
      .catch((error) => if ( error == 'No Token' ) {
      // do no token modal
      } else if ( error === 400 ) {
      // do not authorized modal. etc.
      }

1
Ancak Op'un örneği her zaman bir söz verir. Soru, kullanmanız gerekip gerekmediği Promise.rejectveya throwne zaman reddedilen bir sözü (bir sonrakine atlayacak bir söz) geri vermek istediğinizi belirtmektedir .catch().
Marcos Pereira

@maxwell - Senden hoşlanıyorum örnek. Aynı zamanda getirme sırasında bir yakalama ekleyecek ve içinde istisna fırlatacaksanız, o zaman denemek için güvenli olacaksınız ... yakalamak ... İstisna akışında mükemmel bir dünya yok, ama bence birini kullanmak tek bir desen mantıklıdır ve desenleri birleştirmek güvenli değildir (deseninizle anti-desen benzetmesine karşılık gelir).
user3053247

1
Mükemmel cevap ama burada bir kusur buluyorum - bu desen tüm hataların bir Promise.reject döndürerek ele alındığını varsayar - checkCredentials () 'dan atılabilecek tüm beklenmedik hatalarla ne olur?
chenop

1
Evet haklısın @chenop - denemek / yakalamak için sarmanız gereken beklenmedik hataları yakalamak için
maxwell

@ Maxwell'in durumunu anlamıyorum. Sadece yapmanız öyle yapılandırmak Could checkCredentials(x).then(onFulfilled).catch(e) {}ve sahip catchkolu hem ret kılıf ve atılan hata durumda?
Ben Wheeler

5

Denemek için bir örnek. Atmak yerine reddetme işlevini kullanmak için isVersionThrow öğesini false olarak değiştirin.

const isVersionThrow = true

class TestClass {
  async testFunction () {
    if (isVersionThrow) {
      console.log('Throw version')
      throw new Error('Fail!')
    } else {
      console.log('Reject version')
      return new Promise((resolve, reject) => {
        reject(new Error('Fail!'))
      })
    }
  }
}

const test = async () => {
  const test = new TestClass()
  try {
    var response = await test.testFunction()
    return response 
  } catch (error) {
    console.log('ERROR RETURNED')
    throw error 
  }  
}

test()
.then(result => {
  console.log('result: ' + result)
})
.catch(error => {
  console.log('error: ' + error)
})

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.