Style JavaScript pour les rappels facultatifs

93

J'ai quelques fonctions qui recevront occasionnellement (pas toujours) un rappel et l'exécuteront. Est-ce que vérifier si le rappel est défini / fonctionne est un bon style ou existe-t-il un meilleur moyen?

Exemple:

function save (callback){
   .....do stuff......
   if(typeof callback !== 'undefined'){
     callback();
   };
};
henry.oswald
la source
dans les navigateurs modernes, vous pouvez simplement utiliser, typeof callback !== undefinedalors laissez de côté le'
Snickbrack
1
et si vous appelez simplement save()? Cela ne donnera-t-il pas une erreur ou un avertissement pelucheux parce qu'il manque un argument? Ou c'est parfaitement ok et le rappel est tout simplement undefined?
João Pimentel Ferreira

Réponses:

139

Je préfère personnellement

typeof callback === 'function' && callback();

La typeofcommande est cependant douteuse et ne doit être utilisée que pour "undefined"et"function"

Le problème avec le typeof !== undefinedest que l'utilisateur peut transmettre une valeur définie et non une fonction

Raynos
la source
3
typeofn'est pas douteux. C'est parfois vague, mais je n'appellerais pas cela douteux. Cependant, il est convenu que si vous appelez le rappel en tant que fonction, il est préférable de vérifier qu'il s'agit bien d'une fonction et non, vous savez, d'un numéro. :-)
TJ Crowder
1
@TJCrowder dodgy pourrait être le mauvais mot, il est bien défini mais inutile à cause de la boxe et du retour "object"95% du temps.
Raynos
1
typeofne cause pas la boxe. typeof "foo"est "string", non "object". C'est en fait le seul moyen réel de savoir si vous avez affaire à une chaîne primitive ou à un Stringobjet. (Peut-être que vous pensez à Object.prototype.toString, ce qui est très pratique , mais cause la boxe.)
TJ Crowder
4
Utilisation merveilleusement idiomatique de &&d'ailleurs.
TJ Crowder
@TJCrowder Vous avez raison, je ne sais pas quelle est la valeur de la comparaison des primitives de chaîne et des objets String encadrés. Aussi, je suis d'accord, .toStringc'est charmant pour trouver le [[Class]].
Raynos
50

Vous pouvez également faire:

var noop = function(){}; // do nothing.

function save (callback){
   callback = callback || noop;
   .....do stuff......
};

C'est particulièrement utile si vous utilisez le callbackdans quelques endroits.

De plus, si vous utilisez jQuery, vous avez déjà une fonction comme celle-là, elle s'appelle $ .noop

Pablo Fernandez
la source
4
À mon avis, c'est la solution la plus élégante.
Nicolas Le Thierry d'Ennequin
2
D'accord. Cela facilite également les tests, car il n'y a pas de condition if.
5
mais cela ne résout pas le problème de type n'est-ce pas? et si je passe un tableau? ou une chaîne?
Zerho
1
Simplifier verscallback = callback || function(){};
NorCalKnockOut
1
Vous pouvez également rendre un argument optionnel en lui donnant une valeur par défaut dans la déclaration:function save (callback=noop) { ...
Keith
39

Faites simplement

if (callback) callback();

Je préfère appeler le callback s'il est fourni, quel que soit son type. Ne le laissez pas échouer silencieusement, afin que l'implémenteur sache qu'il a passé un argument incorrect et peut le réparer.

ninja123
la source
11

ECMAScript 6

// @param callback Default value is a noop fn.
function save(callback = ()=>{}) {
   // do stuff...
   callback();
}
Lucio
la source
3

Plutôt que de rendre le rappel facultatif, attribuez simplement une valeur par défaut et appelez-la quoi qu'il arrive

const identity = x =>
  x

const save (..., callback = identity) {
  // ...
  return callback (...)
}

Lorsqu'elle est utilisée

save (...)              // callback has no effect
save (..., console.log) // console.log is used as callback

Un tel style est appelé style de passage continu . Voici un exemple réel combinations, qui génère toutes les combinaisons possibles d'une entrée Array

const identity = x =>
  x

const None =
  Symbol ()

const combinations = ([ x = None, ...rest ], callback = identity) =>
  x === None
    ? callback ([[]])
    : combinations
        ( rest
        , combs =>
            callback (combs .concat (combs .map (c => [ x, ...c ])))
        )

console.log (combinations (['A', 'B', 'C']))
// [ []
// , [ 'C' ]
// , [ 'B' ]
// , [ 'B', 'C' ]
// , [ 'A' ]
// , [ 'A', 'C' ]
// , [ 'A', 'B' ]
// , [ 'A', 'B', 'C' ]
// ]

Parce qu'il combinationsest défini dans le style de passage de continuation, l'appel ci-dessus est effectivement le même

combinations (['A', 'B', 'C'], console.log)
// [ []
// , [ 'C' ]
// , [ 'B' ]
// , [ 'B', 'C' ]
// , [ 'A' ]
// , [ 'A', 'C' ]
// , [ 'A', 'B' ]
// , [ 'A', 'B', 'C' ]
// ]

Nous pouvons également passer une suite personnalisée qui fait autre chose avec le résultat

console.log (combinations (['A', 'B', 'C'], combs => combs.length))
// 8
// (8 total combinations)

Le style de continuation peut être utilisé avec des résultats étonnamment élégants

const first = (x, y) =>
  x

const fibonacci = (n, callback = first) =>
  n === 0
    ? callback (0, 1)
    : fibonacci
        ( n - 1
        , (a, b) => callback (b, a + b)
        )
        
console.log (fibonacci (10)) // 55
// 55 is the 10th fibonacci number
// (0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ...)

Je vous remercie
la source
1

J'étais tellement fatigué de voir ce même extrait encore et encore, j'ai écrit ceci:

  var cb = function(g) {
    if (g) {
      var args = Array.prototype.slice.call(arguments); 
      args.shift(); 
      g.apply(null, args); 
    }
  };

J'ai des centaines de fonctions qui font des choses comme

  cb(callback, { error : null }, [0, 3, 5], true);

ou peu importe...

Je suis sceptique quant à l'ensemble de la stratégie «assurez-vous qu'il fonctionne». Les seules valeurs légitimes sont une fonction ou une fausse. Si quelqu'un passe un nombre non nul ou une chaîne non vide, qu'allez-vous faire? Comment le fait d'ignorer le problème le résout-il?

Malvolio
la source
La fonction peut être surchargée pour accepter une valeur comme premier argument, la fonction comme premier argument ou les deux paramètres. Il existe un cas d'utilisation pour vérifier s'il s'agit d'une fonction. Il est également préférable d'ignorer le mauvais paramètre puis de lancer une erreur pour exécuter une non-fonction
Raynos
@Raynos - C'est un cas d'utilisation très, très spécifique. JavaScript, étant faiblement typé, n'est pas bon pour distinguer les types, et il est généralement préférable de transmettre des paramètres nommés que d'essayer de distinguer les types et de deviner ce que l'appelant veut:save( { callback : confirmProfileSaved, priority: "low" })
Malvolio
Je ne suis pas d'accord, la capacité de vérification de type est suffisante. Je préfère la surcharge de méthode, mais c'est une préférence personnelle. C'est le genre de chose que jQuery fait beaucoup.
Raynos
@Raynos - Il est pire d'ignorer le mauvais paramètre que de lancer une erreur pour exécuter une non-fonction. Prenons un cas typique: la fonction de bibliothèque exécute une activité asynchrone et la fonction appelante doit être notifiée. Donc, un utilisateur non averti, ignorant et échouant semble la même: l'activité semble ne jamais se terminer. Cependant, pour un utilisateur averti (par exemple, le programmeur d'origine), un échec donne un message d'erreur et une trace de pile qui pointe directement sur le problème; ignorer le paramètre signifie une analyse fastidieuse pour déterminer ce qui a causé «rien».
Malvolio
il y a cependant un compromis à faire. Lancer une erreur plantera l'exécution, mais l'ignorer permettra à tout autre code autour d'elle de s'exécuter normalement.
Raynos
1

Une fonction valide est basée sur le prototype Function, utilisez:

if (callback instanceof Function)

pour être sûr que le rappel est une fonction

G. Moore
la source
0

Si le critère d'exécution du rappel est que celui-ci soit défini ou non, alors tout va bien. Aussi, je suggère de vérifier si c'est vraiment une fonction en plus.

Mrchief
la source
0

Je suis sincèrement passé à coffee-script et j'ai trouvé que les arguments par défaut sont un bon moyen de résoudre ce problème

doSomething = (arg1, arg2, callback = ()->)->
    callback()
henry.oswald
la source
0

Cela peut facilement être fait avec ArgueJS :

function save (){
  arguments = __({callback: [Function]})
.....do stuff......
  if(arguments.callback){
    callback();
  };
};
zVictor
la source