Как стать автором
Обновить

Контексты функций в Action script

Время на прочтение3 мин
Количество просмотров3.5K
Я люблю использовать анонимные функции, передавать функции по ссылке, объявлять функции прямо в теле другой функции и т.п. Это удобно и практично, но с этими механизмами могут возникнуть некоторые проблемы. Начиная с версии 9 Flash Player сохраняет в this функции её родителя. Звучит просто, но все ли понимают, что это значит и как тяжело было раньше без этого?

Например, теперь можно описать такую функцию:

public class TestClass
{
	var property : Number;

	function updateValue(value : Number) : void
	{
		TestClass(this).property = value;
	}
}

и передавать её куда угодно:

var func : Function = new TestClass().updateValue;
func(555);

и быть уверенным, где-бы её не вызвали в this будет экземпляр класса TestClass. Но я не об этом, есть более любопытные действия, которые можно производить над функциями в Action Script, их мы их рассмотрим.

Асинхронные вызовы


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

class Example
{
	function updateItem(item : SomeObject) : void
	{
		_tempObject = item;
		new ServerService(onGetResult).getResult(item.startValue);
	}

	function onGetResult(result : Object) : void
	{
		_tempObject.endValue = result;
	}

	private var _tempObject : SomeObject;
}

Всё написано верно, но зачем так сложно? Давайте упростим подобную ерунду, «умным» кодом:

function updateItem(item : SomeObject) : void
{
	new ServerService(onGetResult).getResult(item.startValue);

	function onGetResult(result : Object) : void
	{
		item.endValue = result;
	}
}

В данном случае функция onGetResult имеет доступ ко всем переменным функции updateItem и к её аргументу item в частности. Такой прием во многих случаях может сократить объем кода и убрать негативный оттенок асинхронности. Кстати, в this функции onGetResult будет уже не экземпляр Example, а просто global.

Множественные асинхронные вызовы


Ещё интереснее ситуации когда нужно сделать несколько асинхронных запросов в цикле, а затем обработать каждый ответ соответственно, например:

function updateItems(items : ArrayCollection) : void
{
	for each (var item : SomeObject in items)
	{
		new ServerService(onGetResult).getResult(item.startValue);
	}

	function onGetResult(result : Object) : void
	{
		item.endValue = result;
	}
}

Данным кодом мы не достигнем желаемого результата. В тот момент когда сервер вернёт нам ответы, переменная item будет ссылаться на последний элемент коллекции items и все данные присвоятся только ему, слишком много чести! В таких ситуациях не помогает ни сохраняемый контекст функции ни область видимости переменных родителя, тут нужно что-то другое.

Зачастую можно воспользоваться так называемым Loader-ом:

function updateItems(items : ArrayCollection) : void
{
	for each (var item : SomeObject in items)
	{
		new ValueLoader(item);
	}
}

class ValueLoader
{
	public function ValueLoader(item : SomeObject)
	{
		new ServerService(onGetResult).getResult(item.startValue);

		function onGetResult(result : Object) : void
		{
			item.endValue = result;
		}
	}
}

Так как контекста функции недостаточно что-бы сохранить item для обновления его после ответа сервера, мы создаем над функцией обёртку — класс, которые способен запомнить в контексте всё что нужно. Так как конструктор класса всё та же функция, аргумент item без проблем будет доступен в функции onGetResult.

Стандартизированый объект ContextFunction


В конце концов, если вы нежелаете плодить массу всевозможных Loader-ов, можно ввести универсальный тип — паттерн для многократного использования:

class ContextFunction
{
	public function ContextFunction(targetFunction : Function, ... args)
	{
		_contextArgumnets = args;
		_targetFunction = targetFunction;
	}

	public function func(... args) : void
	{
		var targetArguments : Array = 
			args.concat(_contextArgumnets);
		_targetFunction.apply(this, targetArguments);
	}

	private var _contextArgumnets : Array;

	private var _targetFunction : Function;
}

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

function updateItems(items : ArrayCollection) : void
{
	for each (var item : SomeObject in items)
	{
		new ServerService(new ContextFunction(onGetResult, item).func).
			getResult(item.startValue);
	}
}

function onGetResult(result : Object, item : SomeObject) : void
{
	item.endValue = result;
}

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

В заключение, могу вас уверить, что все эти трюки используются мной на практике очень часто и эффективно. Это не высосанные из пальца проблемы.
Теги:
Хабы:
Всего голосов 26: ↑23 и ↓3+20
Комментарии42

Публикации