Le problème est assez simple. Nous devons imprégner une fonction d'un paramètre, puis extraire simplement ce paramètre du corps de la fonction. Je vais vous présenter le plan dactylographié ...
abstract class Puzzle {
abstract assign(param, fn): any;
abstract getAssignedValue(): any;
async test() {
const wrapped = this.assign(222, async () => {
return 555 + this.getAssignedValue();
});
console.log("Expecting", await wrapped(), "to be", 777);
}
}
Préparons le décor:
assign() doit être une fonction de flèche anonyme qui ne prend aucun paramètre.async . La valeur assignée pourrait simplement être stockée quelque part pendant la durée de l'appel, mais comme la fonction est async et peut avoir des await s, vous ne pouvez pas vous fier à la conservation de la valeur à travers plusieurs appels entrelacés.this.getAssignedValue() ne prend aucun paramètre, renvoyant tout ce que nous avons assigné avec la méthode assign() .Ce serait formidable de trouver une solution plus élégante que celles que j'ai présentées ci-dessous.
Bon, il semble que nous ayons trouvé une bonne solution solide inspirée de zone.js. Le même type de problème y est résolu, et la solution consiste à remplacer la signification de certaines primitives de niveau système, telles que SetTimeout et Promise. Le seul casse-tête ci-dessus était l'instruction async, qui signifiait que le corps de la fonction pouvait être réorganisé efficacement. Les asynchrones sont finalement déclenchées par des promesses, vous devrez donc remplacer votre promesse par quelque chose qui tient compte du contexte. C'est assez compliqué, et comme mon cas d'utilisation est en dehors du navigateur ou même du nœud, je ne vous ennuierai pas avec les détails. Pour la plupart des gens qui rencontrent ce genre de problème, utilisez simplement zone.js.
3 Réponses :
class HackySolution2 extends Puzzle {
assign(param: any, fn: AnyFunction): AnyFunction {
const sub = Object(this);
sub["getAssignedValue"] = () => param;
return function () { return eval(fn.toString()); }.call(sub);
}
getAssignedValue() {
return undefined;
}
}
Dans cette solution, je getAssignedValue() un objet qui remplace la méthode getAssignedValue() et réévalue le code source de la fonction passée, en changeant effectivement la signification de this . Pas encore tout à fait de qualité de production ...
Oups, cela interrompt les fermetures.
Nous avons en fait une mise en œuvre fonctionnelle. C'est un hack tellement douloureux, mais prouve que cela devrait être possible. En quelque sorte. Il y a peut-être même une solution super simple qui me manque simplement parce que je regarde ça depuis trop longtemps.
class HackySolution extends Puzzle {
private readonly repo = {};
assign(param: any, fn) {
// code is a random field for repo. It must also be a valid JS fn name.
const code = 'd' + Math.floor(Math.random() * 1000001);
// Store the parameter with this code.
this.repo[code] = param;
// Create a function that has code as part of the name.
const name = `FN_TOKEN_${code}_END_TOKEN`;
const wrapper = new Function(`return function ${name}(){ return this(); }`)();
// Proceed with normal invocation, sending fn as the this argument.
return () => wrapper.call(fn);
}
getAssignedValue() {
// Comb through the stack trace for our FN_TOKEN / END_TOKEN pair, and extract the code.
const regex = /FN_TOKEN_(.*)_END_TOKEN/gm;
const code = regexGetFirstGroup(regex, new Error().stack);
return this.repo[code];
}
}
L'idée dans notre solution est donc d'examiner la trace de pile de la new Error().stack pile new Error().stack , Et d'encapsuler quelque chose que nous pouvons extraire sous forme de jeton, que nous allons à notre tour mettre dans un dépôt. Hacky? Très piraté.
Les tests montrent que c'est en fait tout à fait réalisable, mais nécessite un environnement d'exécution plus moderne que celui que nous avons - à savoir ES2017 +.
Je ne sais pas dactylographié, donc ce n'est peut-être pas utile, mais qu'en est-il de quelque chose comme:
const build_assign_hooks = () => {
let assignment;
const get_value = () => assignment;
const assign = (param, fn) => {
assignment = param;
return fn;
}
return [assign, get_value];
};
class Puzzle {
constructor() {
const [assign, getAssignedValue] = build_assign_hooks();
this.assign = assign;
this.getAssignedValue = getAssignedValue;
}
async test() {
const wrapped = this.assign(222, async () => {
return 555 + this.getAssignedValue();
});
console.log("Expecting", await wrapped(), "to be", 777);
}
}
const puzzle = new Puzzle();
puzzle.test();Cela ne fonctionne pas pour plusieurs définitions de fonction. Le plus gros problème est quand async peut réorganiser l'exécution et bousiller de telles affectations directes, donc cela ne fonctionne pas vraiment comme une classe de solutions.
Ah merci. Il est clair que je n'ai pas vraiment compris la question. J'ai également essayé de définir un nouvel objet sur this lors de l'appel d'assignation (de sorte que this.getAssignedValue () pointe vers un ancien this), mais (probablement pour de très bonnes raisons) cela ne me permettrait pas de le faire.
C'est peut-être un "casse-tête", mais il s'agit d'un pur problème de programmation sans restrictions stupides, donc je ne vois aucune raison de dire que c'est hors sujet ici.
@Liam C'est un casse-tête dans le sens où c'est déroutant. C'est un problème réel, comme je suis sûr que vous pouvez le voir d'après le contexte.
Il existe des solutions piratées, mais c'est toujours un problème X / Y. S'il s'agit d'un problème réel que vous estimez devoir résoudre, je consacrerais des efforts à modifier les exigences essentiellement cassées plutôt que de proposer des solutions laides.
@certainperformance - Je ne connais aucune solution non piratée, c'est pourquoi je pose la question ici. Ce serait trivial à faire avec une fonction (), mais dans une fonction de flèche, vous ne pouvez pas relier cela. S'il n'y a pas de solution propre, nous devrons évidemment faire quelque chose de différent. Ce n'est pas encore clair quoi. Aussi, peut-être que des solutions proches existent? Nous en avons découvert 2 jusqu'à présent, il y en a peut-être plus. 🤔