J'ai une fonction qui peut renvoyer un résultat de synchronisation ou asynchrone
// all handlers are sync, infer boolean const allHandlersAreOk: boolean = myClassSync.invokeHandlers() // all handlers are async, infer Promise<boolean> const allAsyncHandlersAreOk: Promise<boolean> = await myClassAsync.invokeHandlers() // at least one handler is async, infer Promise<boolean> const allMixedHandlersAreOk: Promise<boolean> = await myClassMix.invokeHandlers()
et une classe qui prend une liste de ces fonctions
const myClassSync = new MyClass<MyType>([ (ctx) => true, (ctx) => false ]); const myClassAsync = new MyClass<MyType>([ async (ctx) => Promise.resolve(true), async (ctx) => Promise.reject() ]); const myClassMix = new MyClass<MyType>([ async (ctx) => Promise.resolve(true), (ctx) => true ]);
Je me demandais s'il y avait une chance de faire en sorte que dactylographié infère le type de retour du invokeHandlers ()
basé sur les gestionnaires donnés. Considérez que tous les gestionnaires sont déclarés au moment du design:
class MyClass<T> { constructor(private handlers: Array<HookHandler<T>>) { } public invokeHandlers() : boolean | Promise<boolean> { // invoke each handler and return: // - Promise<boolean> if exist a handler that return a Promise<T> // - boolean if all handlers are synchronous } }
Puis-je rendre le type de retour de invokeHandlers ()
dépendant des types du courant donné des hanlders sans casting explicite? Donc par exemple
type HookHandler<T> = (context: MyClass<T>) => boolean | Promise<boolean>;
Je peux évidemment renvoyer un simple Promise
, mais je perdrais la possibilité d'appeler les invokeHandlers ( )
dans des contextes synchrones, et il veut éviter cela.
Des suggestions ou un autre choix de conception pour faire face au problème? Merci!
3 Réponses :
Que certains d'entre eux puissent retourner des promesses est un fait. C'est tout ce que TypeScript peut savoir.
Si elles sont ou non toutes les promesses de retour ne peuvent être déterminées qu'au moment de l'exécution.
La réponse est donc non, TypeScript ne peut pas déduire quelque chose qui ne peut être déduit qu'au moment de l'exécution.
Logique. Pourrait être une solution pour vous de diviser le invokeHandlers ()
en deux fonctions, l'une qui renvoie un booléen
et l'autre renvoyant un code Promise
Oui, mais alors celui qui ne peut renvoyer que boolean
devra être tapé de telle manière que ses paramètres ne puissent jamais être des promesses, vous obligeant (en tant que programmeur) à utiliser le bon dès le début . Et cela supprimerait fondamentalement l'avantage de pouvoir mélanger les gestionnaires async et sync. OU vous pouvez effectuer la vérification au moment de l'exécution au lieu de la compilation, mais vous perdez alors les avantages de TypeScript.
Oui, c'était le but. Actuellement, j'ignore les gestionnaires asynchrones si l'appelant utilise la version de synchronisation de invokeHandlers ()
, mais je cherchais une solution plus propre
vous pouvez utiliser des surcharges si vous avez un moyen de différencier vos gestionnaires ou de les identifier d'une manière ou d'une autre au moment de l'exécution
function handler(context: AsyncHandler): Promise<boolean>; function handler(context: MixedHandlers): Promise<boolean>; function handler(context: SyncHandlers): boolean: function handler(context){ // your implementation, maybe instanceof if each type has a class representation }
Donc si vous pouviez écrire quelque chose comme:
function handler(x: number): string; function handler(y: string): number; function handler(arg) { if (typeof arg === 'number') { return `${arg}` } else { return parseInt(arg); } } const inferred = handler(1); // <-- typescript correctly infers string const alsoInferred = handler('1'); // <-- typescript correctly infers number
TypeScript pourrait correctement déduire le type de retour. Je ne sais pas si cela est possible en fonction de votre structure de code, mais j'ai pensé que je partagerais. En savoir plus ici , en particulier la section sur les surcharges
p >
Merci, je vais essayer
Voici comment je l'aborderais:
Trouvez des types séparés pour chaque gestionnaire de hook possible:
const myClassSync = new MyClass([ (ctx) => true, (ctx) => false ]); // all handlers are sync, infer boolean const allHandlersAreOk: boolean = myClassSync.invokeHandlers() const myClassAsync = new MyClass([ async (ctx) => Promise.resolve(true), async (ctx) => Promise.reject() ]); // all handlers are async, infer Promise<boolean> // note you do not "await" it, since you want a Promise const allAsyncHandlersAreOk: Promise<boolean> = myClassAsync.invokeHandlers() const myClassMix = new MyClass([ async (ctx) => Promise.resolve(true), (ctx) => true ]); // at least one handler is async, infer Promise<boolean> // note you do not "await" it, since you want a Promise const allMixedHandlersAreOk: Promise<boolean> = myClassMix.invokeHandlers()
Et puis créez MyClass code > dépendent du type
HH
de HookHandler
que vous utilisez. Le type de retour de invokeHandlers
peut être un type conditionnel qui évalue en booléen
si HH
est SyncHookHandler
, et Promise
si HH
est AsyncHookHandler
ou AsyncHookHandler | SyncHookHandler
:
class MyClass<HH extends HookHandler> { constructor(private handlers: Array<HH>) { } public invokeHandlers(): Promise<boolean> extends ReturnType<HH> ? Promise<boolean> : boolean; public invokeHandlers(): boolean | Promise<boolean> { const rets = this.handlers.map(h => h(this)); const firstPromise = rets.find(r => typeof r !== 'boolean'); if (firstPromise) { return firstPromise; // ð¤·â what do you want to return here } // must be all booleans const allBooleanRets = rets as boolean[]; return allBooleanRets.every(b => b); // ð¤·â what do you want to return here } }
Je viens de faire une implémentation idiote dans invokeHandlers ()
pour donner une idée de ce que vous y feriez . Vous pouvez maintenant voir que votre code se comporte comme prévu
type SyncHookHandler = (context: MyClass<any>) => boolean; type AsyncHookHandler = (context: MyClass<any>) => Promise<boolean>; type HookHandler = AsyncHookHandler | SyncHookHandler;
Est-ce que cela fonctionne pour vous?
Veuillez noter que puisque l'exemple de code n'avait aucune dépendance structurelle sur le paramètre générique T
, j'ai l'a supprimé . Si vous en avez besoin, vous pouvez le rajouter aux endroits appropriés, mais je suppose que la question concerne davantage la détection-sync-si-vous-pouvez-et moins un type générique.
D'accord, j'espère que cela vous aidera; bonne chance!
J'ai peut-être une solution pour vous, mais votre code n'a aucune dépendance structurelle du paramètre
T
, donc je prévois de l'exclure.Le type générique est copié à partir du code réel, ce n'est qu'une simplification