typescript - Bir Promise'in çağırdığı aşırı yüklenmiş bir fonksiyonun türünü kendi çağıranına nasıl iletebileceği?
Bir EventEmitter sınıfını genişlettim ve fonksiyon aşırı yüklemeyi kullandım, böylece addListener belirli bir string ile çağrıldığında, geri çağırma otomatik olarak tiplenmiş olacak:
const codes = {
code1: 'code1',
code2: 'code2',
} as const;
type CodeType = typeof codes;
interface Code1Interface { a: string };
interface Code2Interface { b: boolean };
class MyEmitter { // extends ...
public addListener(code: CodeType['code1'], callback: (event: Code1Interface) => void): void;
public addListener(code: CodeType['code2'], callback: (event: Code2Interface) => void): void;
public addListener(code: CodeType[keyof CodeType], callback: (event: any) => void): void; // fallback
public addListener(code: any, callback: any) {
// call the parent addListener here
}
}
Ancak, addListener'ı bir promise içine kapsülleyen bir fonksiyonum var ve TypeScript dönüş türünü tahmin edemiyor:
function getEvent(code: CodeType[keyof CodeType]) { // returns Promise<unknown>
return new Promise((resolve, reject) => {
const emitter = new MyEmitter();
emitter.addListener(code, (event) => resolve(event));
})
}
getEvent(codes.code1).then(x => x) // x has type unknown
Fonksiyon aşırı yüklemede tanımlanan arayüz türünü getEvent fonksiyonunun çağıranına iletmek veya MyEmitter sınıfında yeniden kullanılabilecek daha genel bir eşleme yapmak mümkün mü?
demo
Cevap #1
Aşırı yüklemeler şu anda iyi bir şekilde genelleştirilemiyor; yalnızca doğrudan çağırdığınızda "doğru" çağrı imzasına çözülüyorlar. Eğer tür çıkarımı gibi daha yüksek düzeyde davranışlar kullanmaya çalışırsanız, derleyici genellikle vazgeçiyor ve sadece son çağrı imzasını seçiyor. Bu zamanla değişebilir (birleşirse işleri geliştirecek olan microsoft/TypeScript#52944 numaralı bir pull request var), ancak TypeScript 5.1 itibarıyla aşırı yüklemelerin manipüle edilmesi zor.
Örnek kodunuz için, aşırı yüklemelerden generiklere geçmeye eğilimli olurdum:
type CodeValMap = {
code1: Code1Interface;
code2: Code2Interface;
[k: string]: any;
}
class MyEmitter {
public addListener<K extends keyof CodeValMap>(
code: K, callback: (event: CodeValMap[K]) => void
): void;
public addListener(code: any, callback: any) {}
}
function getEvent<K extends keyof CodeValMap>(code: K) {
return new Promise<CodeValMap[K]>((resolve, reject) => {
const emitter = new MyEmitter();
emitter.addListener(code, (event) => resolve(event));
})
}
Burada CodeValMap öğesini, string literaller "code1" ve "code2" türlerinden ilgili event türüne bir harita olarak tanımladık (ve ihtiyacınız olursa varsayılan davranışı elde etmek için any işaretine sahip bir indeks imzası ekleyebilirsiniz).
Ve MyEmitter'in addListener metodu, iki ilişkili code ve callback argümanını kabul etmesi için K extends keyof CodeValMap genel tipinde tanımlanmıştır. Sonra getEvent() temelde aynı şekilde yazılmıştır, K extends keyof CodeValMap genel fonksiyonu olarak, ve code tipinin dönüş tipine geçmesi için Promise<CodeValMap[K]> döndürürüz:
getEvent(codes.c1).then(x => x) // (parameter) x: Code1Interface
getEvent(codes.c2).then(y => y) // (parameter) y: Code2Interface
getEvent("hoobydooby").then(z => z) // (parameter) z: any
Güzel görünüyor!
Şunu unutmayın: CodeValMap'i, codes sabitinizden ve codes değerlerinden ilgili arayüzlere elle yazılmış bir tür eşlemesiyle programlı olarak oluşturmak mümkündür, şöyle:
const codes = {
c1: 'code1',
c2: 'code2',
} as const;
type CodeType = typeof codes;
interface CodeKeyMap {
c1: Code1Interface,
c2: Code2Interface
}
type CodeValMap = {
[K in keyof CodeType as CodeType[K]]: CodeKeyMap[K]
} & { [k: string]: any }
O CodeValMap, elle yazılan ile eşdeğerdir, ancak bu form, codes değerleri yerine anahtarlar açısından yazılı tutmak istiyorsanız daha faydalı olabilir.
Koda yönelik oyun alanı bağlantısı
Cevap #2
Bir yerde Promise<T> genel parametresini sağlamanız gerekiyor, callback fonksiyonlardan çıkarılamaz
Doğru ee tipleri için yalnızca tip içeren bir typed-emitter kütüphanesi kullanmanızı öneririm
interface Code1Interface { a: string };
interface Code2Interface { b: boolean };
import TypedEmitter from 'typed-emitter'
import EventEmitter from 'node:events'
type MyEvents = {
code1: (event: Code1Interface) => void
code2: (event: Code1Interface) => void
}
class MyEmitter extends (EventEmitter as new () => TypedEmitter<MyEvents>) {}
function getEvent<Code extends keyof MyEvents>(code: Code) {
return new Promise<Parameters<MyEvents[Code]>[0]>((resolve, reject) => {
const emitter = new MyEmitter();
emitter.addListener(code, (event) => resolve(event));
})
}
let x = await getEvent('code1')
// ^?
// let x: Code1Interface
// placing it into return works as well
function getEvent2<Code extends keyof MyEvents>(code: Code): Promise<Parameters<MyEvents[Code]>[0]> {
return new Promise((resolve, reject) => {
const emitter = new MyEmitter();
emitter.addListener(code, (event) => resolve(event));
})
}
