JavaScript-Stil für optionale Rückrufe

93

Ich habe einige Funktionen, die gelegentlich (nicht immer) einen Rückruf erhalten und ausführen. Ist die Überprüfung, ob der Rückruf definiert ist / funktioniert, ein guter Stil oder gibt es einen besseren Weg?

Beispiel:

function save (callback){
   .....do stuff......
   if(typeof callback !== 'undefined'){
     callback();
   };
};
henry.oswald
quelle
in modernen Browsern könnten Sie einfach verwenden, typeof callback !== undefinedalso lassen Sie die'
Snickbrack
1
und wenn du einfach anrufst save()? Wird das nicht einen Fehler oder eine Flusenwarnung geben, weil ein Argument fehlt? Oder ist es vollkommen in Ordnung und Rückruf ist einfach undefined?
João Pimentel Ferreira

Antworten:

139

Ich persönlich bevorzuge

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

Der typeofBefehl ist jedoch zwielichtig und sollte nur für "undefined"und verwendet werden"function"

Das Problem dabei typeof !== undefinedist, dass der Benutzer möglicherweise einen definierten Wert und keine Funktion übergibt

Raynos
quelle
3
typeofist nicht zwielichtig. Es ist manchmal vage, aber ich würde es nicht zwielichtig nennen. Wir sind uns jedoch einig, dass es am besten ist, zu überprüfen, ob es sich tatsächlich um eine Funktion handelt und nicht um eine Nummer, wenn Sie den Rückruf als Funktion aufrufen möchten. :-)
TJ Crowder
1
@TJCrowder zwielichtig könnte das falsche Wort sein, es ist gut definiert, aber nutzlos, da in "object"95% der Fälle geboxt und zurückgegeben wird.
Raynos
1
typeofverursacht kein Boxen. typeof "foo"ist "string"nicht "object". Dies ist tatsächlich die einzige Möglichkeit, um festzustellen, ob es sich um ein Zeichenfolgenprimitiv oder ein StringObjekt handelt. (Vielleicht denken Sie daran Object.prototype.toString, was sehr praktisch ist , aber Boxen verursacht.)
TJ Crowder
4
Wunderbar idiomatische Verwendung &&übrigens.
TJ Crowder
@TJCrowder Sie haben einen Punkt, ich weiß nicht, welchen Wert der Vergleich von String-Grundelementen und Box-String-Objekten hat. Auch ich stimme zu, .toStringist schön, um das zu finden [[Class]].
Raynos
50

Sie können auch tun:

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

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

Es ist besonders nützlich, wenn Sie das callbackan einigen Stellen verwenden.

Wenn Sie verwenden jQuery, haben Sie bereits eine solche Funktion namens $ .noop

Pablo Fernandez
quelle
4
Aus meiner Sicht ist dies die eleganteste Lösung.
Nicolas Le Thierry d'Ennequin
2
Einverstanden. Dies erleichtert auch das Testen, da keine if-Bedingung vorliegt.
5
aber das behebt nicht das Typproblem, nicht wahr? Was ist, wenn ich ein Array übergebe? oder ein String?
Zerho
1
Vereinfachen Sie zucallback = callback || function(){};
NorCalKnockOut
1
Sie können ein Argument auch optional machen, indem Sie ihm in der Deklaration einen Standardwert geben:function save (callback=noop) { ...
Keith
39

Einfach machen

if (callback) callback();

Ich rufe den Rückruf lieber an, wenn er geliefert wird, egal um welchen Typ es sich handelt. Lassen Sie es nicht stillschweigend scheitern, damit der Implementierer weiß, dass er ein falsches Argument übergeben hat und es beheben kann.

ninja123
quelle
11

ECMAScript 6

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

Anstatt den Rückruf optional zu machen, weisen Sie einfach einen Standard zu und rufen Sie ihn auf, egal was passiert

const identity = x =>
  x

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

Wenn benutzt

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

Ein solcher Stil wird als Continuation-Passing-Stil bezeichnet . Hier ist ein reales Beispiel, combinationsdas alle möglichen Kombinationen einer Array-Eingabe generiert

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' ]
// ]

Da combinationsder obige Aufruf im Continuation-Passing-Stil definiert ist, ist er praktisch derselbe

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

Wir können auch eine benutzerdefinierte Fortsetzung übergeben, die etwas anderes mit dem Ergebnis macht

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

Der Continuation-Passing-Stil kann mit überraschend eleganten Ergebnissen verwendet werden

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

Danke dir
quelle
1

Ich war es so leid, immer wieder denselben Ausschnitt zu sehen, dass ich Folgendes schrieb:

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

Ich habe Hunderte von Funktionen, die Dinge wie tun

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

oder Wasauchimmer...

Ich bin skeptisch gegenüber der gesamten Strategie "Stellen Sie sicher, dass sie funktioniert". Die einzigen legitimen Werte sind eine Funktion oder eine Falschheit. Was tun Sie, wenn jemand eine Zahl ungleich Null oder eine nicht leere Zeichenfolge übergibt? Wie löst das Ignorieren des Problems das Problem?

Malvolio
quelle
Die Funktion kann überladen sein, um entweder einen Wert als erstes Argument, die Funktion als erstes Argument oder beide Parameter zu akzeptieren. Es gibt einen Anwendungsfall, um zu überprüfen, ob es sich um eine Funktion handelt. Es ist auch besser, den falschen Parameter zu ignorieren, als einen Fehler für die Ausführung einer
Nichtfunktion auszulösen
@ Raynos - Das ist ein sehr, sehr spezifischer Anwendungsfall. Da JavaScript schwach typisiert ist, ist es nicht gut, Typen zu unterscheiden, und es ist normalerweise besser, benannte Parameter zu übergeben, als zu versuchen, die Typen zu unterscheiden und zu erraten, was der Anrufer will:save( { callback : confirmProfileSaved, priority: "low" })
Malvolio
Ich bin anderer Meinung, es gibt genug Möglichkeiten zur Typprüfung. Ich bevorzuge das Überladen von Methoden, aber das ist meine persönliche Präferenz. So etwas macht jQuery oft.
Raynos
@ Raynos - Es ist schlimmer , den falschen Parameter zu ignorieren, als einen Fehler beim Ausführen einer Nichtfunktion auszulösen . Stellen Sie sich einen typischen Fall vor: Die Bibliotheksfunktion führt eine asynchrone Aktivität aus und die aufrufende Funktion muss benachrichtigt werden. Ein nicht anspruchsvoller Benutzer, der ignoriert und versagt, scheint dasselbe zu sein: Die Aktivität scheint niemals abgeschlossen zu sein. Für einen erfahrenen Benutzer (z. B. den ursprünglichen Programmierer) gibt ein Fehler jedoch eine Fehlermeldung und eine Stapelverfolgung aus, die direkt auf das Problem hinweisen. Das Ignorieren des Parameters bedeutet eine mühsame Analyse, um festzustellen, was dazu geführt hat, dass "nichts" passiert ist.
Malvolio
Es muss jedoch ein Kompromiss geschlossen werden. Wenn Sie einen Fehler auslösen, stürzt die Laufzeit ab. Wenn Sie ihn jedoch ignorieren, wird der andere Code um ihn herum wie gewohnt ausgeführt.
Raynos
1

Eine gültige Funktion basiert auf dem Funktionsprototyp. Verwenden Sie:

if (callback instanceof Function)

um sicherzugehen, dass der Rückruf eine Funktion ist

G. Moore
quelle
0

Wenn das Kriterium für die Ausführung des Rückrufs lautet, ob er definiert ist oder nicht, ist alles in Ordnung. Außerdem schlage ich vor zu prüfen, ob es wirklich eine Funktion zusätzlich ist.

Mrchief
quelle
0

Ich bin zum Kaffee-Skript übergegangen und habe festgestellt, dass Standardargumente ein guter Weg sind, um dieses Problem zu lösen

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

Mit ArgueJS geht das ganz einfach :

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