Kontext
Kürzlich arbeitete ich an "Promistification" einer Drittanbieter-Bibliothek. Im Grunde ist die Bibliothek voll von NodeJS-asynchronen Stilfunktionen (die Callback als letztes Argument verwenden). I.e. die Funktionen, die Signaturen ähnlich wie diese haben:Typescript Typ Inferenz in einer verallgemeinerten "promisify" -Funktion
function foo(arg1: string, arg2: number, ..., callback: (error, result) => void): void
Ich habe versucht, eine Funktion schreiben würde den Code zum Einwickeln der ursprünglichen Funktionen und machen sie in Promise<T>
Rückkehr diejenigen reduzieren:
function cb<TResult>(
resolve: (res: TResult) => void,
reject: (err: any) => void
): (actualError, actualResult) => void {
return (error, result) => error ? reject(error) : resolve(result);
}
Dann die Methoden promisify, würde ich Code wie das schreiben:
patchUserMetadata(userId: string, userMetadata: any): Promise<a0.Auth0UserProfile> {
return new Promise((resolve, reject) =>
this.wrapped.patchUserMetadata(userId, userMetadata, cb(resolve, reject)));
}
linkUser(userId: string, secondaryUserToken: string): Promise<any> {
return new Promise((resolve, reject) =>
this.wrapped.linkUser(userId, secondaryUserToken, cb(resolve, reject)));
}
// ... and so on, and on, and on...
Wie Sie leicht sehen können, bin ich mit TypeScript immer noch nicht vertraut und versuchte im Grunde, ein Rad neu zu erfinden. Mein Rad endete als ein Sechseck und ich hielt zu viel Verpackung Code von Hand zu schreiben ...
Jemand, der meinen Code überprüft darauf hingewiesen, dass ich ähnliches Ergebnis js-promisify
zu erzielen bei geringeren Kosten nutzen können. Die Bibliothek definiert einen Helfer, der die Arbeit erledigt:
module.exports = function (fun, args, self) {
return new Promise(function (resolve, reject) {
args.push(function (err, data) {
err && reject(err);
resolve(data);
})
fun.apply(self, args);
});
};
Da ich mit Typoskript anstatt JavaScript zu tun habe, ging ich weiter und tat ein wenig Forschung. Dies ist, wie ich typed-promisify
Kommissionierung endete und der Code sah nun wie folgt aus:
patchUserMetadata = promisify(this.wrapped.patchUserMetadata);
linkUser = promisify(this.wrapped.linkUser);
viel ordentlicher, nicht wahr?
Näher
Ich habe mich gefragt, wie genau funktioniert diese Funktion promisify
Arbeit? Ich schaute auf den Quellcode und eine Lösung gefunden, die zu js-promisify
‚s ein ähnlich funktioniert:
export function promisify<T>(f: (cb: (err: any, res: T) => void) => void, thisContext?: any):() => Promise<T>;
export function promisify<A, T>(f: (arg: A, cb: (err: any, res: T) => void) => void, thisContext?: any): (arg: A) => Promise<T>;
export function promisify<A, A2, T>(f: (arg: A, arg2: A2, cb: (err: any, res: T) => void) => void, thisContext?: any): (arg: A, arg2: A2) => Promise<T>;
// ...more overloads
export function promisify(f: any, thisContext?: any) {
return function() {
let args = Array.prototype.slice.call(arguments);
return new Promise((resolve, reject) => {
args.push((err: any, result: any) => err !== null ? reject(err) : resolve(result));
f.apply(thisContext, args);
});
}
}
Frage
Wenn man sich die promisify
genau hinsehen, können Sie sehen, dass diese Lösung nicht wirklich verallgemeinert. Das heißt, wenn ich eine Funktion mit 10 oder mehr Parametern promiziieren müsste, würde es keine passende Überladung geben. Die Implementierung würde immer noch funktionieren, jedoch würde die Typinformation in diesem Fall verloren gehen.
Gibt es eine Möglichkeit in TypeScript, den genauen Funktionstyp (oder Signatur oder Anzahl und Arten von Parametern) abzuleiten, ohne all diese unangenehmen Überlastungen im Voraus zu definieren?
Ich bin für so etwas suchen [offensichtlich, Pseudo-Code]:
export function promisify<...[TArgs], T>(
f: (...allArgsButLastTwo: [TArgs],
cb: (err: any, res: T) => void) => void,
thisContext?: any
): (...[TArgs]) => Promise<T>;
export function promisify(
...allArgsButLastTwo: any[],
f: any,
thisContext?: any
) {
return function() {
let args = Array.prototype.slice.call(arguments);
return new Promise((resolve, reject) => {
args.push((err: any, result: any) => err !== null ? reject(err) : resolve(result));
f.apply(thisContext, args);
});
}
}
Ich habe das Gefühl, dass das, was ich suche nicht erreichbar ist und deshalb die Liste lange Überlast eine war letzter Ausweg/Kompromisslösung, die der Autor verwenden musste.
Vielen Dank für die Bezugnahme auf die TS-Problem und die Roadmap Filipe !! –