Вложенные асинхронные вызовы. Объект Deferred в деталях.
	
    
        
        
	
	
            
        
	
Объект Deferred инкапсулирует последовательность обработчиков для еще не существующего результата, чем сильно упрощает сложные AJAX-приложения. Он предоставляется различными фреймворками (Dojo Toolkit, Mochikit) и отдельными библиотечками (jsDeferred, Promises etc). 
С его помощью можно добавлять обработчики не в момент запуска метода (например, посылки запроса XMLHTTPRequest() , а в любой момент после этого. 
Основные методы объекта Deferred: 
addCallback(функция-обработчик) 
addErrback(функция-обработчик) 
callback(результат) 
errback(результат) 
 
Обычно, когда функция возвращает Deferred, т.е "обещание результата в некоторый момент", программист затем цепляет к нему обработчики результата, которые будут вызваны в той же последовательности, через addCallback/addErrback. 
Код, который управляет объектом Deferred, т.е тот код, который дал обещание предоставить результат, должен вызвать callback() или errback() методы в тот момент, когда результат появится. Например, после завершения некоторой асинхронной операции. 
При этом будет вызван первый в цепочке обработчик, добавленный при помощи addCallback() или addErrback() соответственно. 
Каждый следующий обработчик получит результат, который вернул предыдущий. 
Вот - самый простой пример обработчика: 
var deferred = new Deferred()
deferred.addCallback(function(result) { return result })
Важный принцип при работе с Deferred: каждый обработчик должен возвращать результат. Так что затем всегда можно продолжить работу с этим результатом. 
Например, вот wrapper для XmlHTTPRequest, который возвращает объект Deferred: 
function xhrGet(url) {	
	var deferred = new Deferred()
	var xhr = new XmlHttpRequest()
	xhr.open("GET", url, true); 
	xhr.onreadystatechange = function() {
		if (xhr.readyState!=4) return
		if (xhr.status==200) {
			deferred.callback(xhr.responseText)  
		} else {
			deferred.errback(xhr.statusText)
		}
	}
	xhr.send(null)
	return deferred
}
А внешний код может добавить к нему обработчики для успешно полученного результата и для ошибки: 
var deferred = xhrGet("http://someurl")
deferred.addCallback(function(res) { alert("Result: "+res); return res })
deferred.addErrback(function(err) { alert("Error: "+err); return err })
Внутри Deferred - это просто упорядоченный список пар callback/errback. Методы addCallback, addErrback, addBoth и addCallbacks добавляют в него элементы. 
Например, последовательность обработчиков 
var deferred = new Deferred()
deferred.addCallback(myCallback)
deferred.addErrback(myErrbac)
deferred.addBoth(myBoth)
deferred.addCallbacks(myCallback, myErrback)
 
внутри объекта Deferred  становится списком: 
[
	[myCallback, null],
	[null, myErrback],
	[myBoth, myBoth],
	[myCallback, myErrback]
]
 
Каждый вызов add* добавляет один элемент в список, как показано выше. 
Внутри у Deferred есть одно из трех состояний (свойство "fired"): 
- -1, еще нет результата
 
- 0, есть результат "success"
 
- 1, произошла ошибка "error"
 
 
Deferred приходит в состояние "error" в одном из трех случаев: 
- аргумент 
callback или errback является instanceof Error 
- из последнего обработчика выпал 
exception 
- последний обработчик вернул значение 
instanceof Error 
 
Во всех остальных случаях, Deferred находится в состоянии "success". 
Состояние Deferred определяет, какой обработчик будет вызван из следующего элемента последовательности. Если соответствующее значение равно null (например, надо вызвать errback, а элемент имеет вид [callback, null]), то этот элемент пропускается. 
В случае с обработчиками выше, результат будет обработан примерно так (представьте, что все exceptions перехватываются и возвращаются): 
// d.callback(result) or d.errback(result)
if(!(result instanceof Error)){
	result = myCallback(result);
}
if(result instanceof Error){
	result = myErrback(result);
}
result = myBoth(result);
if(result instanceof Error){
	result = myErrback(result);
}else{
	result = myCallback(result);
}
Полученный результат затем хранится на тот случай, если к последовательности обработчиков будет добавлен новый элемент. Так как результат уже есть, то при добавлении нового обработчика - он тут же будет активирован. 
Обработчики, в свою очередь, могут возвращать объекты Deferred. 
При этом остальная часть цепочки исходного Deferred ждет, пока новый Deferred не вернет значение, чтобы, в зависимости от этого значения, вызвать callback или errback. 
Таким способом можно сделать реализовать последовательность вложенных асинхронных вызовов. 
При создании объекта Deferred можно задавать "canceller" - функцию, которая будет вызвана, если до появления результата произойдет вызов Deferred.cancel. 
С помощью canceller можно реализовать "чистый" обрыв XMLHTTPRequest, и т.п. 
Вызов cancel запустит последовательность обработчиков Deferred с ошибкой CancelledError (если canceller не вернет другой результат), поэтому обработчики errback должны быть готовы к такой ошибке, если, конечно, вызов cancel в принципе возможен. 
Объекты Deferred, как правило, используются, чтобы сделать код асинхронным. Обычно, проще всего описать процесс в обычном, синхронном варианте, и затем разделить код, используя Deferred, чтобы отделить обработку асинхронных операций. 
Например, вместо того, чтобы регистрировать callback-функцию, которая будет вызвана при окончании операции рендеринга, можно просто вернуть Deferred. 
// объявление с callback
function renderLotsOfData(data, callback){
	var success = false
	try{
		for(var x in data){
			renderDataitem(data[x]);
		}
		success = true;
	}catch(e){ }
	if(callback){
		callback(success);
	}
}
// использование объявления с callback
renderLotsOfData(someDataObj, function(success){
	// handles success or failure
	if(!success){
		promptUserToRecover();
	}
})
Использование Deferred в данном случае не упрощает код, но задает стандартный интерфейс для задания и обслуживания любого количества обработчиков результата асинхронной операции. 
Кроме того, Deferred освобождает от беспокойства на тему "а, может, вызов уже произошел?", например, в случае возврата результата из кеша. С Deferred, новые обработчики могут быть добавлены в любой момент, даже если результат уже получен. 
function renderLotsOfData(data){
	var d = new Deferred();
	try{
		for(var x in data){
			renderDataitem(data[x]);
		}
		d.callback(true);
	}catch(e){ 
		d.errback(new Error("rendering failed"));
	}
	return d;
}
// использование Deferred 
renderLotsOfData(someDataObj).addErrback(function(){
	promptUserToRecover();
});
// NOTE: addErrback и addCallback возвращают тот же Deferred,
// так что мы можем тут же добавить в цепочку новые обработчики
// или сохранить deferred для добавления обработчиков позже.
В этом примере renderLotsOfData работает синхронно, так что оба варианта довольно-таки искусственные. Поставим отображение данных в setTimeout (типа идет анимация), чтобы почувствовать, как крут Deferred: 
// Deferred и асинхронная функция
function renderLotsOfData(data){
	var d = new Deferred()
	setTimeout(function(){
		try{
			for(var x in data){
				renderDataitem(data[x]);
			}
			d.callback(true);
		}catch(e){ 
			d.errback(new Error("rendering failed"));
		}
	}, 100);
	return d;
}
// используем Deferred для вызова
renderLotsOfData(someDataObj).addErrback(function(){
	promptUserToRecover()
})
// Заметим, что вызывающий код не потребовалось исправлять
// для поддержки асинхронной работы
Благодаря Deferred - почти не пришлось ничего менять, порядок обработчиков соответствует реально происходящему, в общем - все максимально удобно и наглядно! 
Объект DeferredList расширяет Deferred. 
Он позволяет ставить каллбек сразу на пачку асинхронных вызовов. 
Это полезно, например, для асинхронной загрузки всех узлов на одном уровне javascript-дерева, когда хочется сделать что-то после окончания общей загрузки. 
Объект Deferred есть в javascript-фреймворках Dojo и Mochikit. Кроме того, и там и там есть вспомогательный объект DeferredList. 
Собственно, эта статья написана частично по документации dojo. Автор считает, что имеет на это право, т.к сам приложил руку к этой части разработки данного фреймворка  . Впрочем, из обоих фреймворков Deferred можно легко вынуть и приспособить к собственной библиотеке, если таковая у вас имеется. 
Успешной асинхронной разработки. 
	
	 | 
Что-то я дочитал до конца и понял, что Deffered только при наличии фреймворков есть. Можно было это в начале написать))
> С помощью canceller можно реализовать "чистый" обрыв XMLHTTPRequest, и т.п.
что такое "чистый обрыв XHR"?
не совсем понятно зачем нужен canceler, можете привести пример?
Для отмены любого асинхронного события.
Часто необходимо для работы с вводом в Text-box'ы - когда человек нажал 1ую букву пошёл первый запрос, через несколько миллисекунд человек нажал вторую букву - пошёл второй запрос. Если, например на сервере идёт select с условием where text like '<введённые символы>%' то результат с 2ми буквами может вернуть гораздо меньше строк (в разы) да и выполнится быстрее, скорее всего - в результате получается, что ответ от запроса 2 может придти раньше 1-го, а первый, в свою очередь, может "затереть" второй. Для пользователя это будет выглядеть, так, как будто 2ой запрос не выполнился.
если я правильно понимаю, то мы тут сначала вызываем функцию, а потом добавляем колбэки? Но при асинхронном вызове может получиться, что результат будет получен раньше, чем будет добавлен колбэк.
В таком случае callback будет вызван сразу же после его добавления к объекту Deferred.
Если у Deferred вызвать errback с "не ошибкой", например .errback(10) - по описанию получается что Deferred будет в состоянии "success" и вызовы начнутся с callback- в не errback-функций.
Так ли это? Правильно ли это?
С простыми Deferred всё просто и понятно. Но проблемы начинаются когда начинается вложенность и циклы.
К примеру, есть массив id юзеров
Нужно к примеру, асинхронно вернуть названия групп в которых состоят эти юзеры. (Пример придуман из головы чтобы показать проблему).
предположим, есть в наличии два XMLHttpRequest : getGroupIdByUserId и getGroupNameByGroupId
Просто, не правда ли? Но попробуйте написать код, который бы это делал асинхронно, используя Deferred и остался читабельным.
Объект Deferred инкапсулирует последовательность обработчиков для еще не существующего результата, чем сильно упрощает сложные AJAX-приложения. Он предоставляется различными фреймворками (Dojo Toolkit, Mochikit) и отдельными библиотечками (madalin stunt cars 2, Promises etc).