Estilo JavaScript para devoluciones de llamada opcionales

93

Tengo algunas funciones que ocasionalmente (no siempre) recibirán una devolución de llamada y la ejecutarán. ¿Verificar si la devolución de llamada está definida / funciona es un buen estilo o hay una mejor manera?

Ejemplo:

function save (callback){
   .....do stuff......
   if(typeof callback !== 'undefined'){
     callback();
   };
};
henry.oswald
fuente
en los navegadores modernos, simplemente podría usar, typeof callback !== undefinedasí que '
omita
1
y si simplemente llamas save()? ¿Eso no dará un error o una advertencia porque falta un argumento? ¿O está perfectamente bien y la devolución de llamada es simplemente undefined?
João Pimentel Ferreira

Respuestas:

139

Yo personalmente prefiero

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

El typeofcomando es sin embargo poco fiables y sólo debe utilizarse para "undefined"e"function"

El problema con typeof !== undefinedes que el usuario puede pasar un valor que está definido y no una función

Raynos
fuente
3
typeofno es dudoso. A veces es vago, pero no lo llamaría dudoso. Sin embargo, estoy de acuerdo en que si va a llamar a la devolución de llamada como una función, es mejor verificar que en realidad sea una función y no, ya sabe, un número. :-)
TJ Crowder
1
@TJCrowder dudoso puede ser la palabra equivocada, está bien definida pero es inútil debido al boxeo y los retornos el "object"95% del tiempo.
Raynos
1
typeofno causa boxeo. typeof "foo"es "string", no "object". En realidad, es la única forma real de saber si se trata de una cadena primitiva u Stringobjeto. (Quizás esté pensando en Object.prototype.toString, que es muy útil , pero causa boxeo).
TJ Crowder
4
Por &&cierto, uso maravillosamente idiomático de .
TJ Crowder
@TJCrowder Tienes razón, no sé cuál es el valor de comparar cadenas primitivas y objetos String en caja. También estoy de acuerdo en que .toStringes encantador encontrar el [[Class]].
Raynos
50

También puedes hacer:

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

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

Es especialmente útil si usa el callbacken algunos lugares.

Además, si está utilizando jQuery, ya tiene una función como esa, se llama $ .noop

Pablo fernandez
fuente
4
En mi opinión, esta es la solución más elegante.
Nicolas Le Thierry d'Ennequin
2
Convenido. Esto también facilita las pruebas, porque no existe una condición si.
5
pero esto no aborda el problema del tipo, ¿no? ¿qué pasa si paso una matriz? o una cuerda?
Zerho
1
Simplificar acallback = callback || function(){};
NorCalKnockOut
1
También puede hacer que un argumento sea opcional dándole un valor predeterminado en la declaración:function save (callback=noop) { ...
Keith
39

Simplemente haz

if (callback) callback();

Prefiero llamar a la devolución de llamada si se proporciona, sin importar de qué tipo sea. No permita que falle en silencio, para que el implementador sepa que pasó en un argumento incorrecto y pueda solucionarlo.

ninja123
fuente
11

ECMAScript 6

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

En lugar de hacer que la devolución de llamada sea opcional, simplemente asigne un valor predeterminado y llámelo sin importar qué

const identity = x =>
  x

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

Cuando se utilizan

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

Este estilo se llama estilo de continuación-pase . Aquí hay un ejemplo real combinations, que genera todas las combinaciones posibles de una entrada 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' ]
// ]

Debido a que combinationsse define en estilo de paso de continuación, la llamada anterior es efectivamente la misma

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

También podemos pasar una continuación personalizada que hace algo más con el resultado.

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

El estilo de continuación de pases se puede utilizar con resultados sorprendentemente elegantes

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

Gracias
fuente
1

Me cansé tanto de ver el mismo fragmento una y otra vez que escribí esto:

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

Tengo cientos de funciones que hacen cosas como

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

o lo que sea...

Soy escéptico de toda la estrategia de "asegúrese de que funcione". Los únicos valores legítimos son una función o una falsedad. Si alguien pasa un número distinto de cero o una cadena no vacía, ¿qué vas a hacer? ¿Cómo lo resuelve ignorar el problema?

Malvolio
fuente
La función puede estar sobrecargada para aceptar algún valor como primer argumento, la función como primer argumento o ambos parámetros. Hay un caso de uso para verificar si es una función. También es mejor ignorar el parámetro incorrecto y luego lanzar un error por ejecutar una no función
Raynos
@Raynos: ese es un caso de uso muy, muy específico. JavaScript, al tener un tipo débil, no es bueno para distinguir tipos, y generalmente es mejor pasar parámetros con nombre que tratar de distinguir los tipos y adivinar lo que quiere la persona que llama:save( { callback : confirmProfileSaved, priority: "low" })
Malvolio
No estoy de acuerdo, hay suficiente capacidad de verificación de tipos. Prefiero la sobrecarga de métodos, pero esa es una preferencia personal. Este es el tipo de cosas que hace mucho jQuery.
Raynos
@Raynos: es peor ignorar el parámetro incorrecto que lanzar un error por ejecutar una no función. Considere un caso típico: la función de biblioteca está realizando alguna actividad asincrónica y la función de llamada debe ser notificada. Entonces, un usuario poco sofisticado, ignorar y fallar parece lo mismo: la actividad parece no completarse nunca. Sin embargo, para un usuario sofisticado (por ejemplo, el programador original), una falla le da un mensaje de error y un seguimiento de pila que apunta directamente al problema; ignorar el parámetro significa un análisis tedioso para determinar qué causó que "no pasara nada".
Malvolio
Sin embargo, hay que hacer una compensación. Lanzar un error bloqueará el tiempo de ejecución, pero ignorarlo permitirá que otro código a su alrededor se ejecute normalmente.
Raynos
1

Una función válida se basa en el prototipo de función, utilice:

if (callback instanceof Function)

para asegurarse de que la devolución de llamada sea una función

G. Moore
fuente
0

Si el criterio para ejecutar la devolución de llamada es que esté definido o no, entonces está bien. Además, sugiero verificar si realmente es una función adicional.

Mrchief
fuente
0

Me he movido a coffee-script y encontré que los argumentos predeterminados son una buena manera de resolver este problema

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

Se puede hacer fácilmente con ArgueJS :

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