Стиль JavaScript для дополнительных обратных вызовов

93

У меня есть некоторые функции, которые иногда (не всегда) получают обратный вызов и запускают его. Является ли проверка, определен ли обратный вызов / функция, хорошим стилем или есть лучший способ?

Пример:

function save (callback){
   .....do stuff......
   if(typeof callback !== 'undefined'){
     callback();
   };
};
Генри Освальд
источник
в современных браузерах вы можете просто использовать, typeof callback !== undefinedтак что '
опускайте
1
а если просто позвонить save()? Разве это не выдаст ошибку или предупреждение о линтинге из-за отсутствия аргумента? Или это нормально, а обратный звонок - это просто undefined?
Жуан Пиментел Феррейра,

Ответы:

141

Я лично предпочитаю

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

Однако эта typeofкоманда хитрая и должна использоваться только для "undefined"и"function"

Проблема typeof !== undefinedзаключается в том, что пользователь может передать определенное значение, а не функцию

Райнос
источник
3
typeofне хитрый. Иногда это расплывчато, но я бы не назвал это изворотливым. Однако мы договорились, что если вы собираетесь вызывать обратный вызов как функцию, лучше всего проверить, что это действительно функция, а не число. :-)
TJ Crowder
1
@TJCrowder dodgy может быть неправильным словом, оно хорошо определено, но бесполезно из-за бокса и возврата в "object"95% случаев.
Raynos
1
typeofне вызывает бокса. typeof "foo"есть "string", нет "object". На самом деле это единственный реальный способ узнать, имеете ли вы дело со строковым примитивом или Stringобъектом. (Возможно, вы думаете об этом Object.prototype.toString, что очень удобно , но действительно вызывает бокс.)
TJ Crowder
4
&&Кстати, замечательно идиоматическое употребление .
TJ Crowder
@TJCrowder У вас есть смысл, я не знаю, какое значение имеет сравнение строковых примитивов и упакованных объектов String. Также я согласен, что .toStringэто прекрасно для поиска [[Class]].
Raynos
50

Вы также можете:

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

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

Это особенно полезно, если вы используете callback в нескольких местах.

Кроме того, если вы используете jQuery, у вас уже есть такая функция, она называется $ .noop

Пабло Фернандес
источник
4
На мой взгляд, это наиболее элегантное решение.
Николя Ле Тьерри д'Эннекен
2
Согласовано. Это также упрощает тестирование, поскольку отсутствует условие if.
6
но это не решает проблему типа, не так ли? что, если я передам массив? или строка?
Zerho 03
1
Simplify tocallback = callback || function(){};
NorCalKnockOut
1
Вы также можете сделать аргумент необязательным, указав ему значение по умолчанию в объявлении:function save (callback=noop) { ...
Кейт,
40

Просто делай

if (callback) callback();

Я предпочитаю вызывать обратный вызов, если он указан, независимо от его типа. Не позволяйте ему молча выйти из строя, чтобы разработчик знал, что он передал неверный аргумент, и мог исправить его.

ниндзя123
источник
12

ECMAScript 6

// @param callback Default value is a noop fn.
function save(callback = ()=>{}) {
   // do stuff...
   callback();
}
Лючио
источник
3

Вместо того, чтобы делать обратный вызов необязательным, просто назначьте значение по умолчанию и вызывайте его независимо от того, что

const identity = x =>
  x

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

При использовании

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

Такой стиль называется продолжением передачи . Вот реальный пример, combinationsкоторый генерирует все возможные комбинации ввода массива

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

Поскольку combinationsон определен в стиле передачи продолжения, приведенный выше вызов фактически такой же

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

Мы также можем передать собственное продолжение, которое делает что-то еще с результатом

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

Стиль передачи с продолжением можно использовать с удивительно элегантными результатами.

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

Я так устал видеть один и тот же фрагмент снова и снова, что написал следующее:

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

У меня есть сотни функций, выполняющих такие вещи, как

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

или как там ...

Я скептически отношусь ко всей стратегии «убедитесь, что она работает». Единственные допустимые ценности - это функция или ложь. Если кто-то передаст ненулевое число или непустую строку, что вы собираетесь делать? Как ее решить игнорирование проблемы?

Мальволио
источник
Функция может быть перегружена, чтобы принять какое-то значение в качестве первого аргумента, функцию в качестве первого аргумента или оба параметра. Есть вариант использования для проверки того, является ли это функцией. Также лучше проигнорировать неправильный параметр, чем
выдать
@Raynos - Это очень и очень специфический вариант использования. JavaScript, будучи слабо типизированным, не подходит для различения типов, и обычно лучше передавать именованные параметры, чем пытаться различать типы и угадывать, что хочет вызывающий:save( { callback : confirmProfileSaved, priority: "low" })
Malvolio
Я не согласен, возможностей проверки типов достаточно. Я предпочитаю перегрузку методов, но это личное предпочтение. Это то, чем много занимается jQuery.
Raynos
@Raynos - хуже игнорировать неправильный параметр, чем выдавать ошибку при выполнении нефункции. Рассмотрим типичный случай: библиотечная функция выполняет некоторую асинхронную активность, и вызывающая функция должна быть уведомлена. Таким образом, для неискушенного пользователя игнорирование и неудача кажутся одинаковыми: кажется, что действие никогда не завершается. Однако для опытного пользователя (например, первоначального программиста) сбой выдает сообщение об ошибке и трассировку стека, которые указывают непосредственно на проблему; игнорирование параметра означает утомительный анализ, чтобы определить, что привело к "ничему".
Мальволио
Однако придется пойти на компромисс. Выдача ошибки приведет к сбою во время выполнения, но игнорирование этого позволит другому коду выполнить его как обычно.
Raynos
1

Допустимая функция основана на прототипе функции, используйте:

if (callback instanceof Function)

чтобы убедиться, что обратный вызов - это функция

Г. Мур
источник
0

Если критерием для запуска обратного вызова является тот, определен он или нет, тогда все в порядке. Кроме того, я предлагаю дополнительно проверить, действительно ли это функция.

Господин
источник
0

Я перешел на кофейный скрипт и обнаружил, что аргументы по умолчанию - хороший способ решить эту проблему.

doSomething = (arg1, arg2, callback = ()->)->
    callback()
Генри Освальд
источник
0

Это легко сделать с помощью ArgueJS :

function save (){
  arguments = __({callback: [Function]})
.....do stuff......
  if(arguments.callback){
    callback();
  };
};
zVictor
источник