Javascript-форум (https://javascript.ru/forum/)
-   Ваши сайты и скрипты (https://javascript.ru/forum/project/)
-   -   Событие вызова функции (https://javascript.ru/forum/project/24101-sobytie-vyzova-funkcii.html)

FINoM 17.12.2011 23:45

Событие вызова функции
 
Чисто из интереса, решил узнать, можно ли повесить событие на вызов функции. Как ни странно (для меня), это удалось.

(function(){
	window.callEvent = function(func, callback){
		var callNumber = 0;
		return function(){
			var args = [].slice.call(arguments);
			var result;
			try {
				result = func.apply(this, arguments);
				callNumber++;
			} catch (e) {
				callback(e, args, this, callNumber);
				throw e;
			}
			callback(result, args, this, callNumber);
			return result;				 
		}
	}
	
	var logSomething = function(something){
		console.log(something);
	}
	
	logSomething = callEvent(logSomething, function(result, args, self, callNumber){
		console.log('alertSomething has been called. \n\
			Result is ' + result + '. \n\
			Arguments are ' + args + '. \n\
			Self is ' + self + '. \n\
			Number of calls ' + callNumber + '. \n'
		);
	});
	
	Array.prototype.push = callEvent(Array.prototype.push, function(result, args, self, callNumber){
		console.log('Array.prototype.push has been called. \n\
			Result is ' + result + '. \n\
			Arguments are ' + args + '. \n\
			Self is ' + self);
	});
	
	logSomething('Fuck yeah!');
	logSomething('Fuck yeah!');
	logSomething('Fuck yeah!');
	logSomething('Fuck yeah!');
	logSomething('Fuck yeah!');
	
	var array = [1,2,3,4,5];
	array.push(6);
})();

B@rmaley.e><e 17.12.2011 23:49

Цитата:

Сообщение от FINoM
var func2 = func;

Ненужная строка. И простого func хватит.

FINoM 17.12.2011 23:51

B@rmaley.e><e, точно, спасибо.

FINoM 18.12.2011 00:44

Только вот не могу придумать, как это сделать через Function.prototype.

Gvozd 18.12.2011 01:21

а ну-ка, выкинь исключение в наблюдаемой функции))))

FINoM 18.12.2011 01:31

Не совсем понимаю о чем ты. Кинул исключение, всё остановилось. И что?

trikadin 18.12.2011 01:44

Цитата:

Сообщение от FINoM
Только вот не могу придумать, как это сделать через Function.prototype.

Переопределить?

FINoM 18.12.2011 01:49

Цитата:

Сообщение от trikadin
Переопределить?

Э, как?
Function.prototype.callEvent = function(callback){
	this = function(){...
Выкидывает исключение.

B@rmaley.e><e 18.12.2011 01:54

Цитата:

Сообщение от FINoM
Выкидывает исключение.

Естественно. Изменить this из метода нельзя. В данном случае можно будет только вернуть новую функцию, для которой будет назначен соответствующий обработчик.

trikadin 18.12.2011 02:03

Цитата:

Сообщение от B@rmaley.e><e
Естественно. Изменить this из метода нельзя. В данном случае можно будет только вернуть новую функцию, для которой будет назначен соответствующий обработчик.

Я говорю о том, чтобы при создании ф-ции добавлять к ней коллбек и способ добавления новых коллбеков. Путем сохранения Function в переменную и переопределения его. Вряд ли это реально, но это просто идея.

Gvozd 18.12.2011 02:28

Цитата:

Сообщение от FINoM
Не совсем понимаю о чем ты. Кинул исключение, всё остановилось. И что?

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

FINoM 18.12.2011 02:51

Цитата:

Сообщение от Gvozd
бы, в данном случае поймал бы исключение, предал бы в обработчик события, и затем передал бы его дальше, наверх

Так?
window.callEvent = function(func, callback){
		return function(){
			var args = Array.prototype.slice.call(arguments);
			var result;
			try {
				result = func.apply(this, arguments); 
			} catch (e) {
				result = e;
			}
			callback(result, args, this);
			return result;				 
		}
	}


UPD Хотя нет, бред.

FINoM 18.12.2011 02:51

Цитата:

Сообщение от B@rmaley.e><e
В данном случае можно будет только вернуть новую функцию, для которой будет назначен соответствующий обработчик.

Я о том же.

Gvozd 18.12.2011 03:09

Цитата:

Сообщение от FINoM
Так?

не совсем
window.callEvent = function(func, callback){
		return function(){
			var args = Array.prototype.slice.call(arguments);
			var result;
			try {
				result = func.apply(this, arguments); 
			} catch (e) {
				callback(e, args, this);
				throw e;
			}
			callback(result, args, this);
			return result;				 
		}
	}

FINoM 18.12.2011 03:14

Gvozd,
ок, спасибо.

x-yuri 18.12.2011 03:40

(не обновил страницу перед отправкой)

Цитата:

Сообщение от FINoM
Только вот не могу придумать, как это сделать через Function.prototype.

так что ли? Правда непонятно, зачем эти события отлавливать.

Цитата:

Сообщение от Gvozd
а ну-ка, выкинь исключение в наблюдаемой функции))))

а что должно произойти?

FINoM 18.12.2011 05:09

Цитата:

Сообщение от x-yuri
Правда непонятно, зачем эти события отлавливать.

Для дебага. Вот хочешь ты узнать сколько раз была вызвана функция или метод (кстати, может туда добавить счетчик?), какие аргументы были переданы, каков был результат, каков контекст вызова, ловить исключения (спасибо Гвоздю). И при этом ты совершенно не затрагиваешь функцию, которую тестируешь.

Да, и это просто спортивный интерес. Меня полгода назад спросили, можно ли ловить событие вызова какой-нибудь функции, я покрутил пальцем у виска и сказал "нет".

Цитата:

Сообщение от x-yuri
так что ли?

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

x-yuri 18.12.2011 05:37

Цитата:

Сообщение от FINoM
Для дебага.

для этого есть отладчик (журналирование, в первую очередь)

Цитата:

Сообщение от FINoM
И при этом ты совершенно не затрагиваешь функцию, которую тестируешь.

ради чего усложняем свою жизнь?

Цитата:

Сообщение от FINoM
А что оно делает?

перехватывает событие вызова функции

FINoM 18.12.2011 05:42

Цитата:

Сообщение от x-yuri
перехватывает событие вызова функции

Я ниже вызвал f() и ничего не произошло, кроме, собственно, вызова функции.
Цитата:

Сообщение от x-yuri
ради чего усложняем свою жизнь?

Кто усложняет-то?

B@rmaley.e><e 18.12.2011 12:59

Цитата:

Сообщение от trikadin
Я говорю о том, чтобы при создании ф-ции добавлять к ней коллбек и способ добавления новых коллбеков. Путем сохранения Function в переменную и переопределения его. Вряд ли это реально, но это просто идея.

Ну если один раз обернуть функцию, то в дальнейшем уже можно добавлять события на ту же самую функцию, не создавая новых.
Но как минимум один раз обернуть придётся.

melky 18.12.2011 19:50

можно было бы запилить с помощью __parent__, но это свойство уже склеило лыжи.

как вариант, можно переопределить функцию!

FINoM 18.12.2011 19:51

Цитата:

Сообщение от melky
как вариант, можно переопределить функцию!

Это и делается.

melky 18.12.2011 20:09

Цитата:

Сообщение от FINoM (Сообщение 144182)
Это и делается.

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

trikadin 18.12.2011 23:48

Цитата:

Сообщение от B@rmaley.e><e
Ну если один раз обернуть функцию, то в дальнейшем уже можно добавлять события на ту же самую функцию, не создавая новых.
Но как минимум один раз обернуть придётся.

Я говорил о том, чтобы создавать сразу обёрнутую ф-цию. То есть чтобы a= function(){} сразу давало ф-цию, на которую можно повесить обработчик.

B@rmaley.e><e 19.12.2011 00:42

trikadin, встроенные функции-то всё равно ручками оборачивать придётся.

trikadin 19.12.2011 01:05

Цитата:

Сообщение от B@rmaley.e><e
trikadin, встроенные функции-то всё равно ручками оборачивать придётся.

А, да. Об этом я как-то не подумал.

x-yuri 19.12.2011 01:52

Цитата:

Сообщение от FINoM
Я ниже вызвал f() и ничего не произошло, кроме, собственно, вызова функции.

ok, приведу пример ближе к твоему варианту: http://jsfiddle.net/EFmw6/1/

Цитата:

Сообщение от FINoM
Кто усложняет-то?

наверное ты хотел спросить: "В чем я себе усложняю жизнь?" И я тебе отвечу: 1) ты создаешь средства отладки, которые не лучше уже имеющихся (firebug, например) или не расширяющие их значительно, 2) зачем выносить отладочные операторы в отдельную функцию, если можно сделать это прямо на месте?

trikadin 19.12.2011 01:53

Цитата:

Сообщение от x-yuri
зачем выносить отладочные операторы в отдельную функцию, если можно сделать это прямо на месте?

Чтобы не мешать код в кучу. Мухи - отдельно, котлеты - отдельно.

x-yuri 19.12.2011 01:56

Цитата:

Сообщение от trikadin
Чтобы не мешать код в кучу. Мухи - отдельно, котлеты - отдельно.

это временный код, ты его должен удалить в результате

trikadin 19.12.2011 02:07

Цитата:

Сообщение от x-yuri
это временный код, ты его должен удалить в результате

Ну так это ещё проще было бы. Отдельный файл с дебагом, регэксповый поиск в основном - на убирание переопределения. Имхо, идея неплохая.

FINoM 19.12.2011 02:33

Цитата:

Сообщение от x-yuri
ok, приведу пример ближе к твоему варианту: http://jsfiddle.net/EFmw6/1/

Там всё то же присваивание. Было бы интересно сделать что-то типа такого:

f = function(){
...
}

f.attachCallListener(function{
...
})

f(); //а здесь уже вызывается функция и обработчик
Цитата:

Сообщение от x-yuri
ты создаешь средства отладки, которые не лучше уже имеющихся (firebug, например) или не расширяющие их значительно

А как это потесняет отладчики (или наоборот)?
А можешь, не меняя кода функции, посмотреть, сколько раз и как она вызывалась?

Цитата:

Сообщение от trikadin
Чтобы не мешать код в кучу. Мухи - отдельно, котлеты - отдельно.

Цитата:

Сообщение от x-yuri
это временный код, ты его должен удалить в результате

Точно.

Nekromancer 19.12.2011 03:02

А я бы сделал так:
window.callEvent = function(func, callback){
		return function(){
			var args = Array.prototype.slice.call(arguments);
			var result;
			try {
				result = func.apply(this, arguments); 
			} catch (e) {
				if(callback(e, args, this) !== false) throw e;
			}
			callback(result, args, this);
			return result;				 
		}
	}


FINoM,
Вам бы почитать про Proxy. v8 и SpiderMonkey вполне себе поддерживают.

П.С. Вообще где то на хабре это уже было :)

trikadin 19.12.2011 03:04

Цитата:

Сообщение от Nekromancer
Вам бы почитать про Proxy.

И мне тогда тоже) Пробежался, выглядит интересно)

Nekromancer 19.12.2011 03:10

trikadin,
Кстати там в самом низу есть ссылки на презентацию Брендана Эйча. Сам ещё не смотрел, но надо бы посмотреть :)

trikadin 19.12.2011 03:25

Цитата:

Сообщение от Nekromancer
Кстати там в самом низу есть ссылки на презентацию Брендана Эйча. Сам ещё не смотрел, но надо бы посмотреть

Не осилил ещё до конца) Всё же мой английский пока не идеален)

x-yuri 19.12.2011 06:02

Цитата:

Сообщение от FINoM
Там всё то же присваивание. Было бы интересно сделать что-то типа такого:

f = function(){
...
}

f.attachCallListener(function{
...
})

f(); //а здесь уже вызывается функция и обработчик

ты понимаешь, что attachCallListener надо вставить сразу же после определения f, иначе ты можешь пропустить вызов f? А это значит, что ну пусть ты не изменил код функции, но ты должен изменить код рядом с функцией. А о выносе в другой файл вообще речи не должно быть, по-крайней мере в данной абстрактной ситуации. Так что на практике ты к тому же усложняешь себе жизнь тем, что тебе надо думать, в каком месте можно обернуть функцию, чтобы ничего не пропустить.

потом, даже если бы можно было сделать как ты хочешь, как бы это выглядело? Что-то типа jquery.live?
$('/js/script.js:1:f').live(function() {...});

Как указать вызов какой именно функции надо перехватывать?

Цитата:

Сообщение от FINoM
А как это потесняет отладчики (или наоборот)?
А можешь, не меняя кода функции, посмотреть, сколько раз и как она вызывалась?

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

Цитата:

Сообщение от FINoM
А как это потесняет отладчики (или наоборот)?

в том-то и дело, что никак

Цитата:

Сообщение от FINoM
А можешь, не меняя кода функции, посмотреть, сколько раз и как она вызывалась?

я не знаю, зачем мне это. Все о чем ты говоришь я могу сделать с помощью console.log. Причем я смогу вывести именно тот вариант "как она вызывалась", который мне нужен, а не какой-то универсальный. Я ж так понял, что вывод "как она вызывалась" - это одна из задач твоего инструмента?

если же эта задача представляет академический, а не практический интерес, тогда у меня нету вопросов. Не вижу в этом ничего плохого. Но и интерес к ней у меня тогда автоматически пропадает.

melky 19.12.2011 09:14

Цитата:

Сообщение от Nekromancer (Сообщение 144324)
Брендана Эйча.

Айка. ай-ай-ай.

Nekromancer 19.12.2011 10:19

melky,
ага, стыдно )

FINoM 22.12.2011 02:54

Немного расширил функцию: теперь вместо одного колбека вторым аргументом передается объект
  • before (вызывается перед запуском функции)
  • success (вызывается, если функция отработала успешно)
  • error (если возникла ошибка)
  • after (вызывается в любом случае, не зависимо от успешности выполнения)
Каждому обработчику передается объект
  • args
  • self (контекст)
  • name (имя функции)
  • status ("error" или "success")
  • successNumber (количество успешных вызовов)
  • errorNumber (количество вызовов с ошибкой)
  • result (результат, если есть)
  • error (ошибка, если есть)
Кроме этого, функция addCallListener теперь является частью объекта конструктора Function, во избежание попадания в window или process.

Форкнуть и потестить можно здесь: http://jsfiddle.net/finom/SGhzd/5/


Часовой пояс GMT +3, время: 15:44.