Stile JavaScript per callback opzionali


93

Ho alcune funzioni che occasionalmente (non sempre) riceveranno una richiamata e la eseguiranno. Controllare se il callback è definito / funzione è uno stile buono o c'è un modo migliore?

Esempio:

function save (callback){
   .....do stuff......
   if(typeof callback !== 'undefined'){
     callback();
   };
};

nei browser moderni potresti semplicemente usare, typeof callback !== undefinedquindi tralascia il'
Snickbrack

1
e se chiami semplicemente save()? Questo non darà un errore o un avviso di linting perché manca un argomento? O è perfettamente ok e la richiamata è semplicemente undefined?
João Pimentel Ferreira

Risposte:


139

Personalmente preferisco

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

Il typeofcomando è tuttavia rischioso e dovrebbe essere usato solo per "undefined"e"function"

Il problema con typeof !== undefinedè che l'utente potrebbe passare un valore definito e non una funzione


3
typeofnon è rischioso. A volte è vago, ma non lo definirei rischioso. D'accordo, tuttavia, che se hai intenzione di richiamare la richiamata come una funzione, è meglio controllare che sia effettivamente una funzione e non, sai, un numero. :-)
TJ Crowder

1
@TJCrowder dodgy potrebbe essere la parola sbagliata, è ben definita ma inutile a causa della boxe e del ritorno del "object"95% delle volte.
Raynos

1
typeofnon causa la boxe. typeof "foo"è "string", non "object". In realtà è l'unico vero modo per capire se hai a che fare con una stringa primitiva o un Stringoggetto. (Forse stai pensando Object.prototype.toString, che è molto utile , ma causa boxe.)
TJ Crowder,

4
Uso meravigliosamente idiomatico di &&tra l'altro.
TJ Crowder

@TJCrowder Hai ragione, non so quale sia il valore del confronto tra primitive di stringa e oggetti String in box. Inoltre sono d'accordo .toStringè adorabile per trovare il file [[Class]].
Raynos

50

Puoi anche fare:

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

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

È particolarmente utile se ti capita di utilizzare callbackin pochi posti.

Inoltre, se stai usando jQuery, hai già una funzione del genere, si chiama $ .noop


4
A mio avviso, questa è la soluzione più elegante.
Nicolas Le Thierry d'Ennequin

2
Concordato. Ciò semplifica anche i test, perché non esiste una condizione if.

5
ma questo non risolve il problema del tipo, non è vero? cosa succede se passo un array? o una stringa?
Zerho

1
Semplifica percallback = callback || function(){};
NorCalKnockOut

1
Puoi anche rendere un argomento opzionale dandogli un valore predefinito nella dichiarazione:function save (callback=noop) { ...
Keith

39

Fallo semplicemente

if (callback) callback();

Preferisco richiamare la richiamata se fornita, indipendentemente dal tipo. Non lasciare che fallisca silenziosamente, così l'implementatore sa di aver passato un argomento errato e può aggiustarlo.


11

ECMAScript 6

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

3

Piuttosto che rendere facoltativa la richiamata, assegna semplicemente un valore predefinito e chiamalo qualunque cosa accada

const identity = x =>
  x

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

Quando viene utilizzato

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

Tale stile è chiamato stile di passaggio di continuazione . Ecco un esempio reale combinations, che genera tutte le possibili combinazioni di un input 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' ]
// ]

Poiché combinationsè definito nello stile del passaggio di continuazione, la chiamata di cui sopra è effettivamente la stessa

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

Possiamo anche passare una continuazione personalizzata che fa qualcos'altro con il risultato

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

Lo stile di passaggio continuo può essere utilizzato con risultati sorprendentemente eleganti

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, ...)


1

Mi sono stancato di vedere lo stesso frammento più e più volte ho scritto questo:

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

Ho centinaia di funzioni che fanno cose come

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

o qualunque cosa ...

Sono scettico sull'intera strategia "assicurati che funzioni". Gli unici valori legittimi sono una funzione o il falso. Se qualcuno passa un numero diverso da zero o una stringa non vuota, cosa farai? Come risolverlo ignorando il problema?


La funzione potrebbe essere sovraccaricata per accettare un valore come primo argomento, la funzione come primo argomento o entrambi i parametri. C'è un caso d'uso per verificare se si tratta di una funzione. È anche meglio ignorare il parametro sbagliato, quindi generare un errore per l'esecuzione di una non funzione
Raynos

@ Raynos - Questo è un caso d'uso molto, molto specifico. JavaScript, essendo debolmente tipizzato, non è bravo a distinguere i tipi, e di solito è meglio passare parametri con nome piuttosto che cercare di distinguere i tipi e indovinare cosa vuole il chiamante:save( { callback : confirmProfileSaved, priority: "low" })
Malvolio

Non sono d'accordo, c'è abbastanza capacità di controllo del tipo. Preferisco il sovraccarico del metodo, ma questa è una preferenza personale. Questo è il genere di cose che jQuery fa molto.
Raynos

@ Raynos - È peggio ignorare il parametro sbagliato, quindi generare un errore per l'esecuzione di una non funzione. Si consideri un caso tipico: la funzione di libreria sta eseguendo un'attività asincrona e la funzione chiamante deve essere notificata. Quindi un utente non sofisticato, ignorare e fallire sembra la stessa cosa: l'attività sembra non completarsi mai. Tuttavia, a un utente sofisticato (ad esempio, il programmatore originale), un errore fornisce un messaggio di errore e un'analisi dello stack che puntano direttamente al problema; ignorare il parametro significa analisi noiosa per determinare cosa ha causato "nulla".
Malvolio

c'è però un compromesso da fare. La generazione di un errore bloccherà il run-time, ma ignorarlo consentirà l'esecuzione normale di altro codice attorno ad esso.
Raynos

1

Una funzione valida si basa sul prototipo della funzione, utilizzare:

if (callback instanceof Function)

per essere sicuri che il callback sia una funzione


0

Se il criterio per eseguire la richiamata è che sia definito o meno, allora stai bene. Inoltre, suggerisco di verificare se è davvero una funzione aggiuntiva.


0

Sono passato a coffee-script e ho scoperto che gli argomenti predefiniti sono un bel modo per risolvere questo problema

doSomething = (arg1, arg2, callback = ()->)->
    callback()

0

Può essere fatto facilmente con ArgueJS :

function save (){
  arguments = __({callback: [Function]})
.....do stuff......
  if(arguments.callback){
    callback();
  };
};
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.