Styl JavaScript dla opcjonalnych wywołań zwrotnych

93

Mam kilka funkcji, które od czasu do czasu (nie zawsze) odbierają wywołanie zwrotne i je uruchamiają. Czy sprawdzenie, czy callback jest zdefiniowana / funkcja jest dobrym stylem, czy jest lepszy sposób?

Przykład:

function save (callback){
   .....do stuff......
   if(typeof callback !== 'undefined'){
     callback();
   };
};
henry.oswald
źródło
w nowoczesnych przeglądarkach możesz po prostu użyć, typeof callback !== undefinedwięc pomiń'
Snickbrack
1
a jeśli po prostu zadzwonisz save()? Czy to nie spowoduje błędu lub ostrzeżenia o lintingu z powodu braku argumentu? Czy jest w porządku, a oddzwonienie jest po prostu undefined?
João Pimentel Ferreira

Odpowiedzi:

140

Osobiście wolę

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

typeofPolecenia jest jednak ryzykowna i powinny być wykorzystywane tylko dla "undefined"i"function"

Problem z tym typeof !== undefinedpolega na tym, że użytkownik może przekazać zdefiniowaną wartość, a nie funkcję

Raynos
źródło
3
typeofnie jest podejrzana. Czasami jest niejasny, ale nie nazwałbym tego podejrzanym. Uzgodniono jednak, że jeśli zamierzasz wywołać funkcję zwrotną jako funkcję, najlepiej sprawdzić, czy jest to faktycznie funkcja, a nie, wiesz, numer. :-)
TJ Crowder
1
@TJCrowder podejrzany może być niewłaściwym słowem, jest dobrze zdefiniowany, ale bezużyteczny z powodu boksu i zwrotów w "object"95% przypadków.
Raynos,
1
typeofnie powoduje boksu. typeof "foo"jest "string", nie "object". Właściwie to jedyny prawdziwy sposób, w jaki możesz stwierdzić, czy masz do czynienia z prymitywem łańcuchowym, czy Stringobiektem. (Być może myślisz o tym Object.prototype.toString, co jest bardzo przydatne , ale powoduje boks).
TJ Crowder,
4
Cudownie idiomatyczne użycie &&przy okazji.
TJ Crowder
@TJCrowder Masz rację, nie wiem, jaka jest wartość porównywania prymitywów łańcuchowych i obiektów typu String w pudełkach. Zgadzam się również, że .toStringjest cudownie znaleźć [[Class]].
Raynos,
50

Możesz też:

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

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

Jest to szczególnie przydatne, jeśli zdarzy ci się używać callbackw kilku miejscach.

Dodatkowo, jeśli używasz jQuery, masz już taką funkcję, nazywa się $ .noop

Pablo Fernandez
źródło
4
Moim zdaniem jest to najbardziej eleganckie rozwiązanie.
Nicolas Le Thierry d'Ennequin
2
Zgoda. Ułatwia to również testowanie, ponieważ nie ma warunku if.
6
ale to nie rozwiązuje problemu z typem, prawda? co jeśli przekażę tablicę? czy String?
Zerho
1
Uprość docallback = callback || function(){};
NorCalKnockOut
1
Możesz również ustawić argument jako opcjonalny, nadając mu domyślną wartość w deklaracji:function save (callback=noop) { ...
Keith
40

Po prostu zrób

if (callback) callback();

Wolę wywołać oddzwonienie, jeśli jest dostępne, bez względu na jego typ. Nie pozwól mu zawieść po cichu, aby osoba wdrażająca wie, że przekazał niepoprawny argument i może to naprawić.

ninja123
źródło
12

ECMAScript 6

// @param callback Default value is a noop fn.
function save(callback = ()=>{}) {
   // do stuff...
   callback();
}
Lucio
źródło
3

Zamiast ustawiać funkcję zwrotną jako opcjonalną, po prostu przypisz wartość domyślną i wywołaj ją bez względu na wszystko

const identity = x =>
  x

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

Kiedy jest używany

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

Taki styl nazywa się stylem przechodzenia kontynuacji . Oto prawdziwy przykład, combinationsktóry generuje wszystkie możliwe kombinacje danych wejściowych 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' ]
// ]

Ponieważ combinationsjest zdefiniowane w stylu przekazywania kontynuacji, powyższe wywołanie jest w rzeczywistości takie samo

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

Możemy również przekazać niestandardową kontynuację, która robi coś innego z wynikiem

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

Styl przekazywania kontynuacji może być stosowany z zaskakująco eleganckimi wynikami

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

Dziękuję Ci
źródło
1

Byłem tak zmęczony ciągłym oglądaniem tego samego fragmentu, który napisałem:

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

Mam setki funkcji wykonujących takie rzeczy jak

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

lub cokolwiek...

Jestem sceptyczny co do całej strategii „upewnij się, że to funkcja”. Jedyne słuszne wartości to funkcja lub fałsz. Jeśli ktoś poda niezerową liczbę lub niepusty łańcuch, co zamierzasz zrobić? W jaki sposób ignorowanie problemu rozwiązuje go?

Malvolio
źródło
Funkcja może być przeciążona, aby zaakceptować jakąś wartość jako pierwszy argument, funkcję jako pierwszy argument lub oba parametry. Istnieje przypadek użycia do sprawdzenia, czy jest to funkcja. Lepiej też zignorować niewłaściwy parametr, a następnie
zgłosić
@Raynos - To bardzo, bardzo specyficzny przypadek użycia. JavaScript, będąc słabo wpisane, nie jest dobry w odróżniania rodzajów, a to zwykle lepiej zdać w nazwanych parametrów niż próbując rozróżnić rodzaje i zgadnijcie co rozmówca chce:save( { callback : confirmProfileSaved, priority: "low" })
Malvolio
Nie zgadzam się, jest wystarczająca umiejętność sprawdzania typu. Wolę przeciążanie metod, ale to osobiste preferencje. Jest to rodzaj rzeczy, które często robi jQuery.
Raynos,
@Raynos - Gorzej jest zignorować zły parametr niż zgłosić błąd przy wykonywaniu niefunkcji. Rozważmy typowy przypadek: funkcja biblioteki wykonuje pewną asynchroniczną aktywność i funkcja wywołująca musi zostać powiadomiona. Tak więc nieskomplikowany użytkownik, ignorowanie i niepowodzenie wydaje się to samo: działanie wydaje się nigdy nie zakończyć. Jednak dla wyrafinowanego użytkownika (na przykład pierwotnego programisty) błąd powoduje wyświetlenie komunikatu o błędzie i śladu stosu wskazującego bezpośrednio na problem; ignorowanie parametru oznacza żmudną analizę w celu ustalenia, co spowodowało, że „nic” się nie wydarzyło.
Malvolio
trzeba jednak dokonać kompromisu. Zgłoszenie błędu spowoduje awarię środowiska wykonawczego, ale zignorowanie go umożliwi normalne wykonanie innego kodu wokół niego.
Raynos,
1

Prawidłowa funkcja jest oparta na prototypie funkcji, użyj:

if (callback instanceof Function)

aby upewnić się, że wywołanie zwrotne jest funkcją

G. Moore
źródło
0

Jeśli kryterium uruchomienia wywołania zwrotnego jest takie, czy jest zdefiniowane, czy nie, wszystko jest w porządku. Proponuję też sprawdzić, czy to naprawdę funkcja dodatkowa.

Mrchief
źródło
0

Zgrzeszyłem i przeszedłem do coffee-script i stwierdziłem, że domyślne argumenty to dobry sposób na rozwiązanie tego problemu

doSomething = (arg1, arg2, callback = ()->)->
    callback()
henry.oswald
źródło
0

Można to łatwo zrobić za pomocą ArgueJS :

function save (){
  arguments = __({callback: [Function]})
.....do stuff......
  if(arguments.callback){
    callback();
  };
};
zVictor
źródło