web-dev-qa-db-fra.com

Vous utilisez Function.prototype.bind avec un tableau d’arguments?

Comment appeler Function.prototype.bind avec un tableau d'arguments, par opposition aux arguments codés en dur? (Ne pas utiliser ECMA6, donc pas d’opérateur de propagation).

J'essaie de placer un wrapper de promesses autour d'un module qui utilise des rappels et je veux lier tous les arguments transmis à ma méthode wrapper et les lier. Ensuite, je souhaite appeler la fonction liée partiellement appliquée avec mon propre rappel, ce qui résout ou rejette une promesse.

var find = function() {
  var deferred, bound;
  deferred = Q.defer();
  bound = db.find.bind(null, arguments);
  bound(function(err, docs) {
    if(err) {
      deferred.fail(err);
    } else {
      deferred.resolve(docs);
    }
  });
  return deferred.promise;
}

Mais évidemment, cela ne fonctionne pas parce que bind attend des arguments plutôt qu'un tableau d'arguments. Je sais que je pourrais le faire en insérant mon rappel à la fin du tableau d'arguments et en utilisant apply:

arguments[arguments.length] = function(err, docs) { ... }
db.find.apply(null, arguments);

Ou en parcourant le tableau d'arguments et en reliant la fonction pour chaque argument:

var bound, context;
for(var i = 0; i < arguments.length; i++) {
   context = bound ? bound : db.find;
   bound = context.bind(null, arguments[i]);
}
bound(function(err, docs) { ... })

Mais ces deux méthodes se sentent sales. Des idées?

45
Dan Prince

.bind est une fonction normale, vous pouvez donc appeler .apply dessus.
Tout ce que vous avez à faire est de passer la fonction d'origine en tant que premier paramètre et la variable THIS souhaitée en tant que premier élément du tableau d'arguments:

bound = db.find.bind.apply(db.find, [null].concat(arguments));
//      ^-----^            ^-----^   THIS

Que cela puisse ou non être considéré comme plus propre est laissé au lecteur.

75
Felix Kling

Voici un extrait de code commun que j'utilise dans tous mes projets:

var bind = Function.bind;
var call = Function.call;

var bindable = bind.bind(bind);
var callable = bindable(call);

La fonction bindable peut maintenant être utilisée pour passer un tableau à bind comme suit:

var bound = bindable(db.find, db).apply(null, arguments);

En fait, vous pouvez mettre en cache bindable(db.find, db) pour accélérer la liaison comme suit:

var findable = bindable(db.find, db);
var bound = findable.apply(null, arguments);

Vous pouvez utiliser la fonction findable avec ou sans tableau d'arguments:

var bound = findable(1, 2, 3);

J'espère que cela t'aides.

11
Aadit M Shah

La réponse de Felix n'a pas fonctionné pour moi parce que l'objet arguments n'est pas vraiment un tableau (comme l'a souligné Otts). La solution pour moi était simplement de changer bind et apply:

bound = db.find.apply.bind(db.find, null, arguments);
9
Joel

Pour ceux qui utilisent ES6, Babel compile:

db.find.bind(this, ...arguments)

à:

db.find.bind.apply(db.find, [this].concat(Array.prototype.slice.call(arguments)));

Je pense qu'il est juste de dire que Babel est assez définitif. Merci à @ Lorenz-Lo-Sauer, il est presque identique.

4
nathancahill

Pourquoi ne pas simplement lier le tableau d’arguments selon votre exemple et laisser la fonction bound() la traiter comme ça, comme un tableau ?

Selon l'apparence de votre utilisation, vous transmettez ensuite une fonction en tant qu'argument final à bound(), ce qui signifie qu'en transmettant le tableau d'arguments actuel, vous évitez de séparer les arguments des rappels à l'intérieur de bound(), ce qui facilite potentiellement la lecture.

1
Matt Way

Je trouve que les réponses suivantes sont plus propres que celles acceptées

Function.bind.apply(db.find, [null].concat(arguments));
1
brillout

Si quelqu'un recherche un échantillon abstrait:

var binded = hello.apply.bind(hello,null,['hello','world']);

binded();

function hello(a,b){
  console.log(this); //null
  console.log(a); //hello
  console.log(b); //world
}
1
Paweł

Généralement, ce schéma suffit:

//obj = db
//fnName = 'find'
var args = [this||null].concat(Array.prototype.slice.apply(arguments);
obj[fnName].bind.apply(obj[fnName], args);
1
Lorenz Lo Sauer

Vous venez d'avoir une idée alternative, appliquez partiellement la valeur null au contexte, puis utilisez apply pour appeler la fonction partiellement appliquée.

bound = db.find.bind.bind(null).apply(null, arguments);

Cela supprime le besoin de [null].concat() dans la réponse de @ Felix, qui a l'air légèrement sinistre.

0
Dan Prince

Une réponse simple et définitive pourrait être

Function.apply.bind(this.method, this, arguments);

Un peu "difficile" à saisir, mais soigné.

0
131