Я пишу «модуль» глобальной обработки ошибок для одного из своих приложений.
Одна из функций, которые я хочу иметь, - это иметь возможность легко обернуть функцию блоком 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, что-то там пропускаю, боюсь, может что-то сломается.
Лично вместо того, чтобы загрязнять встроенные объекты, я бы использовал технику декоратора:
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», и исходные аргументы. Но вы не возвращаете его результат, делая упаковку неполной. Но не имеет значения, оборачиваете ли вы функцию, не возвращающую значение.
Не знаю, можно ли назвать этот метод «makeSafe». Это действительно создает ложное впечатление, что использование исключений «безопасно».
Что касается загрязнения пространств имен, я собираюсь загрязнить их еще немного ... Поскольку все, что происходит в JS, инициируется каким-либо событием, я планирую вызвать свою волшебную функцию-оболочку из метода Prototype Event.observe (), поэтому мне не нужно вызывать ее везде.
Я, конечно, вижу недостатки всего этого, но этот конкретный проект в любом случае сильно привязан к Prototype, и я хочу, чтобы этот код обработчика ошибок был как можно более глобальным, так что это не имеет большого значения.
Спасибо за Ваш ответ!
Помимо непреднамеренного конфликта имен, загрязнение встроенных объектов может привести к странным ошибкам на уровне языка. Вот почему работа над новым стандартом JavaScript возродила дискуссии о «закрытии» встроенных объектов для модификаций => код, ориентированный на будущее, должен избегать этой техники.
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 не уверен, я просто обновил весь свой ответ и избавился от сращивания.
Обертка функций по старинке:
//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);
Как вы будете обрабатывать аргументы, переданные в вашей обернутой функции?
Следующая служебная программа-оболочка принимает функцию и позволяет разработчику внедрить код или обернуть оригинал:
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.
Хорошо, так что помимо предпочтения загрязнять или нет ... Ваш последний фрагмент кода выглядит идентично моему. Должен ли я понимать из этого, что мой код действительно работает с объектами и ключевым словом this? Спасибо!