#javascript #jquery #jquery-deferred
#javascript #jquery #jquery-отложенный
Вопрос:
У меня есть стандартный объект javascript, прототип которого расширен с .start()
помощью метода, принимающего 2 обратных вызова в качестве аргументов: success
и failure
соответственно. Этот метод выполняет некоторую асинхронную обработку (это не AJAX) и в зависимости от результата этой обработки вызывает либо успешные, либо неудачные обратные вызовы.
Вот как это можно схематизировать:
function MyObject() {
}
MyObject.prototype.start = function(successCallback, errorCallback) {
(function(s, e) {
window.setTimeout(function() {
if (Math.random() < 0.8) {
s();
} else {
e();
}
}, 2000);
})(successCallback, errorCallback);
}
На самом деле не важна точная обработка, выполняемая внутри метода, важно только, чтобы она была асинхронной и неблокирующей. Я не могу контролировать момент времени, когда метод start завершит обработку. Я также не контролирую прототип и реализацию этого метода.
То, что я контролирую success
failure
, — это обратные вызовы and . Я должен их предоставить.
Теперь у меня есть массив этих объектов:
var arr = [ new MyObject(), new MyObject(), new MyObject() ];
Порядок элементов в этом массиве важен. Мне нужно последовательно запускать .start()
метод для каждого элемента массива, но только после завершения предыдущего (т. Е. Был вызван успешный обратный вызов). И если возникает ошибка (вызывается обратный вызов сбоя) Я хочу остановить выполнение и больше не вызывать метод .start для оставшихся элементов массива.
Я мог бы реализовать это наивно, используя рекурсивную функцию:
function doProcessing(array, index) {
array[index ].start(function() {
console.log('finished processing the ' index ' element');
if (index < array.length) {
doProcessing(array, index);
}
}, function() {
console.log('some error ocurred');
});
}
doProcessing(arr, 0);
Это работает нормально, но, глядя на отложенный объект jQuery, который был представлен в jQuery 1.5, я думаю, что есть место для улучшения этого кода. К сожалению, я пока не очень комфортно себя чувствую с этим, и я пытаюсь его изучить.
Итак, мой вопрос в том, можно ли адаптировать мой наивный код и воспользоваться преимуществами этого нового API, и если да, не могли бы вы дать мне несколько советов?
Вот jsfiddle с моей реализацией.
Комментарии:
1. 1 Всегда приятно видеть, что лучший ответчик задает хороший вопрос. Автоответчик на этот вопрос?
Ответ №1:
Вы могли бы сделать что-то вроде этого: (jsFiddle)
function MyObject() {
}
MyObject.prototype.start = function(queue) {
var deferred = $.Deferred();
//only execute this when everything else in the queue has finished and succeeded
$.when.apply(jQuery,queue).done(function() {
window.setTimeout(function() {
if (Math.random() < 0.8) {
deferred.resolve();
} else {
deferred.reject();
}
}, 2000);
});
return deferred;
}
var arr = [ new MyObject(), new MyObject(), new MyObject() ];
var queue = new Array();
$.each(arr, function(index, value) {
queue.push(value.start(queue)
.done(function() {
console.log('succeeded ' index);
})
.fail(function() {
console.log('failed ' index);
}));
});
Однако не совсем уверен, считаете ли вы это улучшением.
Комментарии:
1. Это выглядит интересно, единственная проблема в том, что я не могу изменить
MyObject
.2. Кстати, какова ваша конечная цель? Истинная сила отложенных вызовов заключается в том, что вы можете зарегистрировать несколько обратных вызовов в очереди обратного вызова. Таким образом, независимо от того, что (a) синхронная функция уже завершена, вы все равно можете зарегистрировать дополнительные обратные вызовы. Кроме того, очень легко зарегистрировать несколько обратных вызовов. Похоже ли это на то, что вам нужно?
3. моя истинная цель — узнать, как работает Deffered, пытаясь понять, можно ли применить его к какому-либо сценарию. Ваш ответ очень полезен в этом направлении. Спасибо.
Ответ №2:
Когда мы программируем, очень важно помнить принципы или рекомендации GRASP.
http://en.wikipedia.org/wiki/GRASP_ (объектно-ориентированный дизайн)
Получить высокую когезию и низкую связь означает, что наш код будет лучше, его будет легче использовать повторно и его будет проще поддерживать.
Итак, класс MyObject не должен знать о существовании очереди. MyObject будет знать свои собственные функции и методы и многое другое.
// Class MyObject
function MyObject(name) {
this.name = name;
}
MyObject.prototype.start = function() {
var deferred = $.Deferred();
var self = this;
setTimeout(function() {
if (Math.random() <= 0.8) {
console.log(self.name "... ok");
deferred.resolve();
} else {
console.log(self.name "... fail");
deferred.reject();
}
}, 1000);
return deferred.promise();
}
Функция main / caller будет знать о существовании MyObject и создаст три экземпляра, которые будут выполняться последовательно.
// Create array of instances
var objectArray = [ new MyObject("A"), new MyObject("B"), new MyObject("C") ];
// Create array of functions to call start function
var functionArray = [];
$.each(objectArray, function(i, obj) {
functionArray.push(
function() {
return obj.start();
}
);
});
// Chain three start calls
$.iterativeWhen(functionArray[0], functionArray[1], functionArray[2])
.done(function() {
console.log("First: Global success");
// Chain three start calls using array
$.iterativeWhen.apply($, functionArray)
.done(function() {
console.log("Second: Global success");
})
.fail(function() {
console.log("Second: Global fail");
});
})
.fail(function() {
console.log("First: Global fail");
});
Я создал плагин для jQuery: iterativeWhen. Он работает с jQuery 1.8 и более поздними версиями.
$.iterativeWhen = function () {
var deferred = $.Deferred();
var promise = deferred.promise();
$.each(arguments, function(i, obj) {
promise = promise.then(function() {
return obj();
});
});
deferred.resolve();
return promise;
};
Jsfiddle здесь: http://jsfiddle.net/WMBfv /
Ответ №3:
В вашей реализации нет ничего плохого. И, как мы все знаем, использование jQuery не всегда является лучшим методом.
Я бы сделал это так: (без необходимости изменять класс MyObject ..)
function doProcessing(array, index) {
var defer = new $.Deferred();
$.when(defer).then(doProcessing);
array[index ].start(function() {
log('finished processing the ' index ' element');
if (index < array.length) {
defer.resolve(array, index);
}
}, function() {
log('some error ocurred => interrupting the process');
});
};
Как вы можете видеть, нет никакого реального преимущества перед простым методом JavaScript. 🙂
Вот моя скрипка: http://jsfiddle.net/jwa91/EbWDQ /