Как обернуть функцию в Javascript?

Я пишу «модуль» глобальной обработки ошибок для одного из своих приложений.

Одна из функций, которые я хочу иметь, - это иметь возможность легко обернуть функцию блоком try{} catch{}, чтобы все вызовы этой функции автоматически имели код обработки ошибок, который будет вызывать мой глобальный метод ведения журнала. (Чтобы избежать загрязнения кода повсюду блоками try / catch).

Однако это немного выходит за рамки моего понимания низкоуровневого функционирования JavaScript, методов .call и .apply и ключевого слова this.

Я написал этот код на основе метода Prototype Function.wrap:

Object.extend(Function.prototype, {
  TryCatchWrap: function() {
    var __method = this;
    return function() {
            try { __method.apply(this, arguments) } catch(ex) { ErrorHandler.Exception(ex); }
    }
  }
});

Что используется так:

function DoSomething(a, b, c, d) {
    document.write(a + b + c)
    alert(1/e);
}

var fn2 = DoSomething.TryCatchWrap();
fn2(1, 2, 3, 4);

Этот код работает отлично. Он выводит 6, а затем вызывает мой глобальный обработчик ошибок.

У меня вопрос: сломается ли это что-нибудь, если функция, которую я оборачиваю, находится внутри объекта и использует оператор "this"? Немного волнуюсь, так как звоню .apply, что-то там пропускаю, боюсь, может что-то сломается.

Асинхронная передача данных с помощью sendBeacon в JavaScript
Асинхронная передача данных с помощью sendBeacon в JavaScript
В современных веб-приложениях отправка данных из JavaScript на стороне клиента на сервер является распространенной задачей. Одним из популярных...
Принципы ООП в JavaScript
Принципы ООП в JavaScript
Парадигма объектно-ориентированного программирования имеет 4 основных принципа,
Laravel с Turbo JS
Laravel с Turbo JS
Turbo - это библиотека JavaScript для упрощения создания быстрых и высокоинтерактивных веб-приложений. Она работает с помощью техники под названием...
Слишком много useState? Давайте useReducer!
Слишком много useState? Давайте useReducer!
Современный фронтенд похож на старую добрую веб-разработку, но с одной загвоздкой: страница в браузере так же сложна, как и бэкенд.
Типы данных JavaScript
Типы данных JavaScript
В JavaScript существует несколько типов данных, включая примитивные типы данных и ссылочные типы данных. Вот краткое объяснение различных типов данных...
CSS Flex: что должен знать каждый разработчик
CSS Flex: что должен знать каждый разработчик
CSS Flex: что должен знать каждый разработчик Модуль flexbox, также известный как гибкий модуль разметки box, помогает эффективно проектировать и...
34
0
59 444
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Ответ принят как подходящий

Лично вместо того, чтобы загрязнять встроенные объекты, я бы использовал технику декоратора:

var makeSafe = function(fn){
  return function(){
    try{
      return fn.apply(this, arguments);
    }catch(ex){
      ErrorHandler.Exception(ex);
    }
  };
};

Вы можете использовать это так:

function fnOriginal(a){
  console.info(1/a);
};

var fn2 = makeSafe(fnOriginal);
fn2(1);
fn2(0);
fn2("abracadabra!");

var obj = {
  method1: function(x){ /* do something */ },
  method2: function(x){ /* do something */ }
};

obj.safeMethod1 = makeSafe(obj.method1);
obj.method1(42);     // the original method
obj.safeMethod1(42); // the "safe" method

// let's override a method completely
obj.method2 = makeSafe(obj.method2);

Но если вы хотите изменить прототипы, вы можете написать это так:

Function.prototype.TryCatchWrap = function(){
  var fn = this; // because we call it on the function itself
  // let's copy the rest from makeSafe()
  return function(){
    try{
      return fn.apply(this, arguments);
    }catch(ex){
      ErrorHandler.Exception(ex);
    }
  };
};

Очевидным улучшением будет параметризация makeSafe (), чтобы вы могли указать, какую функцию вызывать в блоке catch.

Хорошо, так что помимо предпочтения загрязнять или нет ... Ваш последний фрагмент кода выглядит идентично моему. Должен ли я понимать из этого, что мой код действительно работает с объектами и ключевым словом this? Спасибо!

Daniel Magliola 29.11.2008 00:24

Да: вы передаете обернутому методу и «this», и исходные аргументы. Но вы не возвращаете его результат, делая упаковку неполной. Но не имеет значения, оборачиваете ли вы функцию, не возвращающую значение.

Eugene Lazutkin 29.11.2008 06:53

Не знаю, можно ли назвать этот метод «makeSafe». Это действительно создает ложное впечатление, что использование исключений «безопасно».

Marie 17.04.2019 22:14

Что касается загрязнения пространств имен, я собираюсь загрязнить их еще немного ... Поскольку все, что происходит в JS, инициируется каким-либо событием, я планирую вызвать свою волшебную функцию-оболочку из метода Prototype Event.observe (), поэтому мне не нужно вызывать ее везде.

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

Спасибо за Ваш ответ!

Помимо непреднамеренного конфликта имен, загрязнение встроенных объектов может привести к странным ошибкам на уровне языка. Вот почему работа над новым стандартом JavaScript возродила дискуссии о «закрытии» встроенных объектов для модификаций => код, ориентированный на будущее, должен избегать этой техники.

Eugene Lazutkin 29.11.2008 06:58

Object.extend (Function.prototype, { Object.extend в консоли Google Chrome дает мне "undefined" Вот рабочий пример:

    Boolean.prototype.XOR =
//  ^- Note that it's a captial 'B' and so
//      you'll work on the Class and not the >b<oolean object
        function( bool2 ) { 

           var bool1 = this.valueOf();
           //         'this' refers to the actual object - and not to 'XOR'

           return (bool1 == true   &&   bool2 == false)
               || (bool1 == false   &&   bool2 == true);
        } 

alert ( "true.XOR( false ) => " true.XOR( false ) );

Так что вместо Object.extend (Function.prototype, {...}) Сделайте это как: Function.prototype.extend = {}

2017 ответ: просто используйте ES6. Учитывая следующую демонстрационную функцию:

function doThing(){
  console.info(...arguments)
}

Вы можете создать свою собственную функцию-оболочку без использования внешних библиотек:


function wrap(someFunction){
  function wrappedFunction(){
    var newArguments = [...arguments]
    newArguments.push('SECRET EXTRA ARG ADDED BY WRAPPER!')
    console.info(`You're about to run a function with these arguments: \n  ${newArguments}`)
    return someFunction(...newArguments)
  }
  return wrappedFunction
}

В использовании:

doThing('one', 'two', 'three')

Работает нормально.

Но используя новую обернутую функцию:

const wrappedDoThing = wrap(doThing)
wrappedDoThing('one', 'two', 'three')

Возврат:

one two three SECRET EXTRA ARG ADDED BY WRAPPER!

Ответ 2016: используйте модуль wrap:

В приведенном ниже примере я обертываю process.exit(), но он прекрасно работает с любой другой функцией (включая JS браузера).

var wrap = require('lodash.wrap');

var log = console.info.bind(console)

var RESTART_FLUSH_DELAY = 3 * 1000

process.exit = wrap(process.exit, function(originalFunction) {
    log('Waiting', RESTART_FLUSH_DELAY, 'for buffers to flush before restarting')
    setTimeout(originalFunction, RESTART_FLUSH_DELAY)
});

process.exit(1);

Почему вы сращиваете аргументы?

coler-j 16.11.2020 22:26

@ coler-j не уверен, я просто обновил весь свой ответ и избавился от сращивания.

mikemaccana 17.11.2020 18:24

Обертка функций по старинке:

//Our function
function myFunction() {
  //For example we do this:
  document.getElementById('demo').innerHTML = Date();
  return;
}

//Our wrapper - middleware
function wrapper(fn) {
  try {
    return function(){
      console.info('We add something else', Date());
      return fn();
    }
  }
  catch (error) {
    console.info('The error: ', error);
  }
}

//We use wrapper - middleware
myFunction = wrapper(myFunction);

То же и в стиле ES6:

//Our function
let myFunction = () => {
  //For example we do this:
  document.getElementById('demo').innerHTML = Date();
  return;
}

//Our wrapper - middleware
const wrapper = func => {
  try {
    return () => {
      console.info('We add something else', Date());
      return func();
    }
  }
  catch (error) {
    console.info('The error: ', error);
  }
}

//We use wrapper - middleware
myFunction = wrapper(myFunction);

Как вы будете обрабатывать аргументы, переданные в вашей обернутой функции?

sumitroy 02.02.2021 06:15

Следующая служебная программа-оболочка принимает функцию и позволяет разработчику внедрить код или обернуть оригинал:


function wrap(originalFunction, { inject, wrapper } = {}) {

    const wrapperFn = function(...args) {
        if (typeof inject === 'function') {
            inject(originalFunction, this);
        }
        if (typeof wrapper === 'function') {
            return wrapper(originalFunction, this, args);
        }
        return originalFunction.apply(this, args);
    };

    // copy the original function's props onto the wrapper
    for(const prop in originalFunction) {
      if (originalFunction.hasOwnProperty(prop)) {
        wrapperFn[prop] = originalFunction[prop];
      }
    }
    return wrapperFn;
}

Пример использования:


// create window.a()
(function() {

    const txt = 'correctly'; // outer scope variable
    
    window.a = function a(someText) { // our target
        if (someText === "isn't") {
            throw('omg');
        }
        return ['a', someText, window.a.c, txt].join(' ');
    };
    
    window.a.c = 'called'; // a.c property example
})();

const originalFunc = window.a;
console.info(originalFunc('is')); // logs "a is called correctly"

window.a = wrap(originalFunc);
console.info(a('is')); // logs "a is called correctly"

window.a = wrap(originalFunc, { inject(func, thisArg) { console.info('injected function'); }});
console.info(a('is')); // logs "injected function\na is called correctly"

window.a = wrap(originalFunc, { wrapper(func, thisArg, args) { console.info(`doing something else instead of ${func.name}(${args.join(', ')})`); }});
console.info(a('is')); // logs "doing something else instead of a(is)"

window.a = wrap(originalFunc, {
    wrapper(func, thisArg, args) {
        try {
            return func.apply(thisArg, args);
        } catch(err) {
            console.error('got an exception');
        }
    }
});
a("isn't"); // error message: "got an exception"

Последний пример демонстрирует, как обернуть вашу функцию предложением try-catch.

Другие вопросы по теме