我有一個等待“ EventEmitter”的非泛型函式(不是真的,因為我需要在其他具有once但不擴展的類上使用它EventEmitter):
export function waitEvent(emitter: { once: Function }, event: string): Promise<any[]> {
return new Promise((resolve) => {
emitter.once(event, (...args: any[]) => {
resolve(args);
});
});
}
我如何使它通用?
例如,假設 Typescript 知道變數page可以發出loaded帶有 args的事件(title: string)。const [title] = await waitEvent(page, 'loaded')對于它知道的代碼,我該如何做到這title一點string?
這是一個真實世界的例子:
import {chromium} from "playwright";
const browser = await chromium.launch();
const page = await browser.newPage();
page.once('popup', async (page) => {
await page.click('h1');
});
const [popup] = await waitEvent(page, 'popup');
await popup.click('h1');
常規.once呼叫中的用法 know page 是 a Page,使用我的原始版本和@jcalz 的方法waitEvent思考后的那個,所以第二次呼叫不起作用。anyWorker.click
uj5u.com熱心網友回復:
天真的一階呼叫簽名waitEvent()看起來像這樣:
declare function waitEvent<K extends string, A extends any[]>(emitter: {
once(event: K, cb: (...args: A) => void): void
}, event: K): Promise<A>;
只要您使用正確形狀waitEvent()的方法呼叫發射器once(),編譯器就應該能夠A為您推斷:
const thing = {
once(event: "loaded", cb: (title: string) => void) { }
}
const [title] = await waitEvent(thing, "loaded");
title // string
const otherThing = {
once(event: "exploded", cb: (blastRadius: number) => void) { }
}
const [rad] = await waitEvent(otherThing, "exploded");
rad // number
但不幸的是,這在我能想到的大多數現實世界的情況下都會減弱。TypeScript 對表達和操作高階型別沒有太多支持。在您的情況下,once(event, callback)您的事件發射器方法的引數可能無法使用簡單的獨立型別(如上面顯示的thing和otherThing示例)撰寫。
相反,它們是相互關聯的。所以如果event是,比如說,,"loaded"那么callback是型別(title: string) => void。但是如果event是別的東西(我會說,呃,"exploded"),那么callback將是其他型別的(比如,(blastRadius: number) => void)。這意味著thing和otherThing示例需要組合成一個可以處理多種呼叫方式的物件once()。
在 TypeScript 中有不同的方式來表達這種關系,而且這些方式大多不利于型別操作。傳統上,您會使用具有多個呼叫簽名的多載來執行此操作:
interface MyEventEmitter {
once(event: "loaded", cb: (title: string) => void): void;
// ... more ... //
once(event: "exploded", cb: (blastRadius: number) => void): void;
}
這似乎是“劇作家”圖書館正在做的事情。當您實際直接呼叫once()type 的值時,這很棒MyEmitter:
declare const emitter: MyEventEmitter;
emitter.once("loaded", title => title.toUpperCase()); // okay
emitter.once("exploded", rad => rad.toFixed(2)); // okay
但是 TypeScript 無法回答“如果我once()使用event型別引數呼叫"loaded",那么型別是什么cb”的問題,除非您實際上直接呼叫它。TypeScript 無法輕易告訴您once()'event和 arg 型別之間的完整關系cb。
這是一個設計限制,在microsoft/TypeScript#32164(以及其他地方)進行了討論。一般來說,如果你探測 的型別once(),例如在多載函式型別上使用實用Parameters<T>程式型別,你只會得到關于最后一次呼叫簽名的資訊:
type OnceParams = Parameters<MyEventEmitter['once']>;
// type OnceParams = [event: "exploded", cb: (blastRadius: number) => void]
// (where is the information abut the other call signatures)?
所以上面的waitEvent()失敗:
const [title] = await waitEvent(emitter, "loaded");
title // number ?!!?!
關于此問題和解決方法有很多問題(例如,請參閱 多載函式的泛型引數不包含所有選項),但它們都不漂亮,它們只能處理有限的最大數量的呼叫簽名。如果你打算這樣做,你可能只想放棄emitter任意型別,而是硬編碼waitEvent()實際預期的emitter.
撰寫上述內容的另一種方式是,如果在某些映射介面中once()是通用的,如下所示:
interface EventArgs {
loaded: [title: string],
exploded: [blastRadius: number]
}
interface MyEventEmitter {
once<K extends keyof EventArgs>(event: K, cb: (...args: EventArgs[K]) => void): void;
}
同樣,當您實際直接呼叫once()type 的值時非常棒MyEmitter:
declare const emitter: MyEventEmitter;
emitter.once("loaded", title => title.toUpperCase()); // okay
emitter.once("exploded", rad => rad.toFixed(2)); // okay
但是 TypeScript仍然無法回答“如果我once()使用event型別引數呼叫"loaded",那么型別是什么cb”的問題,除非您實際上直接呼叫它。
這與多載版本的限制略有不同;在這種情況下,也許microsoft/TypeScript#40179是一個更好的來源。但這是同樣的基本問題;該功能不容易被探測到。
所以waitEvent()仍然會做錯事,盡管錯誤的事情略有不同:
const [title] = await waitEvent(emitter, "loaded");
title // string | number (better, I guess?, but not great)
在這里,編譯器放棄并只是推斷K和A作為它們的獨立約束EventEmitter。
我能想象到的唯一另一種表示方式是使用 元組型別聯合的rest 引數:
interface EventArgs {
loaded: [title: string],
exploded: [blastRadius: number]
}
type OnceArgs = { [K in keyof EventArgs]:
[event: K, cb: (...args: EventArgs[K]) => void]
}[keyof EventArgs];
/* type OnceArgs =
[event: "loaded", cb: (title: string) => void] |
[event: "exploded", cb: (blastRadius: number) => void]
*/
interface MyEventEmitter {
once(...args: OnceArgs): void;
}
這種行為類似于多載,甚至在 IntelliSense 中顯示為多載,但由于一些限制(請參閱microsoft/TypeScript#42987),使用起來不太好:
declare const emitter: MyEventEmitter;
emitter.once("loaded", title => title.toUpperCase()); // error!
emitter.once("loaded", (title: string) => title.toUpperCase()) // okay
emitter.once("exploded", rad => rad.toFixed(2)); // error!
emitter.once("exploded", (rad: number) => rad.toFixed(2)); // okay
const [title] = await waitEventNaive(emitter, "loaded"); // error!!!
所以這可能是一個非首發。盡管如此,至少可以對其進行檢查并waitEvent相應地重寫:
declare function waitEvent<
A extends [string, (...args: any) => void],
K extends A[0]
>(emitter: {
once(...args: A): void
}, event: K): Promise<Parameters<Extract<A, [K, any]>[1]>>;
const [title] = await waitEvent(emitter, "loaded");
title // string
const [rad] = await waitEvent(emitter, "exploded");
rad // number
萬歲,我猜。但是你的第三方庫使用這種技術的機會很低(也許它更容易使用?)。
這意味著您可能應該做的只是弄清楚將是什么型別emitter,并撰寫一個目標版本,waitEvent()其中輸入和輸出型別之間的硬編碼關系。在上面的示例中,它看起來像:
declare function waitEventHardCoded(
emitter: MyEventEmitter, event: "loaded"): Promise<[title: string]>;
declare function waitEventHardCoded(
emitter: MyEventEmitter, event: "exploded"): Promise<[blastRadius: number]>;
然后這將起作用:
const [title] = await waitEventHardCoded(emitter, "loaded");
title.toUpperCase();
const [rad] = await waitEventHardCoded(emitter, "exploded");
rad.toFixed(2);
這并不令人滿意,但這是我會做的,除非 TypeScript 中對高階函式型別操作的支持得到顯著改善。
Playground 代碼鏈接
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/529772.html
標籤:打字稿仿制药
