Typescript: Neden Promise<A | B>, Promise<A> | Promise<B> olarak atanamaz?


type A = Promise<string> | Promise<number>
type B = Promise<string | number>

const a: A = Math.random() > 0.5 ? Promise.resolve('string') : Promise.resolve(1)
const b: B = Promise.resolve(Math.random() > 0.5 ? 'string' : 1)

class C {
  fa(a: A) {
    console.log(a)
  }

  fb(b: B) {
    console.log(b)
  }
}
const c = new C()

c.fa(a)
c.fb(b)

c.fa(b) /* <-- This produces an error:
Argument of type 'B' is not assignable to parameter of type 'A'.
  Type 'Promise<string | number>' is not assignable to type 'Promise<string>'.
    Type 'string | number' is not assignable to type 'string'.
      Type 'number' is not assignable to type 'string'.ts(2345)
*/
c.fb(a) // This works

Bu kod neden hata veriyor?

Not: Typescript 4.9.4

Cevap #1

Genel olarak, F<T> | F<U>'nin F<T | U> ile eşdeğer olduğu doğru değildir; bunun için F'nin tip argümanında kovaryant olması ve bunun tersinin de doğru olması gerekir (varyansın açıklaması için TypeScript'te Varyans, Kovaryans, Kontravaryans ve Bivaryans arasındaki Farklara bakın). Ve derleyici, bu formlar eşdeğer olsa bile birini diğerine zorla dönüştürmek zorunda değildir.

Özellikle, Promise<T> | Promise<U> ve Promise<T | U> güvenle değiştirilebilir. Peki soru şudur: Neden bu TypeScript'te gerçekleşmiyor?

Yetkili cevabın microsoft/TypeScript#51293'te bulunabileceği anlaşılıyor. TS geliştirici ekibi liderine göre:

Önerilen indirgeme kuralı [Promise<T> | Promise<U> ile Promise<T | U> eşdeğerliliği] bugün geçerlidir, ama bundan ne yapmam gerektiğinden emin değilim.

TC39'un yarın, Promise'lerin geri çağrıyı yeni bir şekilde çalıştıran .thenAgain metoduna sahip olması gerektiğine karar vermesini gerçekten engelleyen hiçbir şey yok ve o noktada Promise<T | U> === Promise<T> | Promise<U> kuralı geçerliliğini yitirecek. O zaman oldukça zor bir durumda oluruz.

Bu özel kural, açıkça herkes bir şekilde Promiseler ile etkileşimde bulunmasına rağmen, diğer geri bildirimlerde duymadığımız bir şeydir ve en azından bazı geçerli programları reddetmeyen hiçbir tür denetleyici yok, bu yüzden genel olarak burada bir eylem maddesi görmüyorum.

Yani cevap, 'düşük talep ile potansiyel geleceğe hazırlık sorunlarının birleşimi' gibi görünüyor.

Cevap #2

Hata mesajına göre:

‘string | number’ türü, ‘string’ türüne atanamaz


Bunu küme birleşimleri açısından düşündüğünüzde daha kolay olur.

  • A, (1) string döndüren tüm sözlerin kümesi ile (2) number döndürenlerin kümesinin birleşimidir.
  • B, string veya number döndürebilecek tüm promise’lerin kümesidir.

Biri diğerinin bir alt kümesidir, aynı küme değiller.

Bu şuna benzer:

  • Dünyadaki tüm beyin cerrahları + tüm roket mühendisleri.
  • Hem beyin cerrahı hem de roket mühendisi olan tüm insanlar.

Şimdi eğer sadece yapabilecekleri işe (beyin ameliyatı yapmak veya fırlatılabilir roketler tasarlamak) önem veriyorsanız, iki grubun üretebileceği olası işin aynı olduğu konusunda haklısınız.

Bu durum burada da geçerlidir:

a.then(x => ...) // (parameter) x: string | number
b.then(y => ...) // (parameter) y: string | number