如何在 Javascript 中包装函数?

Posted

技术标签:

【中文标题】如何在 Javascript 中包装函数?【英文标题】:How do I wrap a function in Javascript? 【发布时间】:2010-09-24 12:07:27 【问题描述】:

我正在为我的一个应用程序编写一个全局错误处理“模块”。

我想要的功能之一是能够轻松地用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,在那里传递了一些东西,我担心这可能会破坏一些东西。

【问题讨论】:

【参考方案1】:

我个人不会污染内置对象,而是使用装饰器技术:

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

你可以这样使用它:

function fnOriginal(a)
  console.log(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 块中调用什么函数。

【讨论】:

好的,所以除了污染与否的偏好...您的最后一个代码 sn-p 看起来与我的相同。我是否应该从中了解我的代码确实适用于对象和“this”关键字?谢谢! 是的:您将“this”和原始参数都传递给包装的方法。但是你不返回它的结果,使包装不完整。但是如果你包装一个不返回值的函数也没关系。 我不知道我会称这种方法为“makeSafe”。这确实给人一种错误的印象,即使用异常是“安全的”。【参考方案2】:

2017 年回答:只使用 ES6。给定以下演示函数:

function doThing()
  console.log(...arguments)

您可以制作自己的包装函数而无需外部库:


function wrap(someFunction)
  function wrappedFunction()
    var newArguments = [...arguments]
    newArguments.push('SECRET EXTRA ARG ADDED BY WRAPPER!')
    console.log(`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.log.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);

【讨论】:

你为什么要拼接args? @coler-j 不确定,我刚刚更新了我的整个答案并摆脱了拼接。【参考方案3】:

Object.extend(Function.prototype, Google Chrome 控制台中的 Object.extend 给了我“未定义” 那么这里有一些工作示例:

    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 =

【讨论】:

【参考方案4】:

老式的函数包装:

//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);

【讨论】:

你将如何处理在你的包装函数中传递的参数?【参考方案5】:

以下包装实用程序采用函数并使开发人员能够注入代码或包装原始代码:


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.log(originalFunc('is')); // logs "a is called correctly"

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

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

window.a = wrap(originalFunc,  wrapper(func, thisArg, args)  console.log(`doing something else instead of $func.name($args.join(', '))`); );
console.log(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 子句包装你的函数

【讨论】:

【参考方案6】:

这是一个 ES6 风格:

const fnOriginal = (a, b, c, d) => 
    console.log(a);
    console.log(b);
    console.log(c);
    console.log(d);
    return 'Return value from fnOriginal';
;


const wrapperFunction = fn => 
    return function () 
        try 
            const returnValuFromOriginal = fn.apply(this, arguments);
            console.log('Adding a new line from Wrapper :', returnValuFromOriginal);
         catch (ex) 
            ErrorHandler.Exception(ex);
        
    ;
;

const fnWrapped = wrapperFunction(fnOriginal);
fnWrapped(1, 2, 3, 4);

【讨论】:

【参考方案7】:

至于污染命名空间,我实际上要污染它们一些...... 由于 JS 中发生的一切都是由某种事件引发的,因此我计划从 Prototype Event.observe() 方法中调用我的神奇包装函数,因此我不需要到处调用它。

当然,我确实看到了这一切的不利之处,但是这个特定的项目无论如何都与 Prototype 密切相关,而且我确实希望这个错误处理程序代码尽可能全局化,所以这没什么大不了的。

感谢您的回答!

【讨论】:

除了意外的名称冲突外,污染内置对象可能会导致语言级别的奇怪错误。这就是为什么在新的 JavaScript 标准上工作重新引发了关于“关闭”内置对象以进行修改的讨论 => 面向未来的代码应该避免这种技术。

以上是关于如何在 Javascript 中包装函数?的主要内容,如果未能解决你的问题,请参考以下文章

如何在函数中包装一段代码? [关闭]

如何在异步协程中包装同步函数?

JavaScript数组中包含数组如何去重?

在 useState 中包装一个函数

在 PHP 函数中包装 MySQL 查询

在 C 中包装 alloca 函数