Как использовать отложенный объект jQuery с пользовательскими объектами JavaScript? - PullRequest
13 голосов
/ 12 октября 2011

У меня есть стандартный объект 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. Это зависит от меня, чтобы обеспечить их.

Теперь у меня есть массив этих объектов:

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 с моей реализацией.

Ответы [ 3 ]

4 голосов
/ 12 октября 2011

Вы можете сделать что-то вроде этого: ( 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);
        }));
});

Не совсем уверен, что вы бы посчитали это улучшением.

3 голосов
/ 13 июля 2013

Когда мы программируем, очень важно помнить принципы или указания GRASP.

http://en.wikipedia.org/wiki/GRASP_(object-oriented_design)

Получение High Cohesion и Low Coupling означает, что наш код будет лучше, более пригоден для повторного использования и проще в обслуживании.

Итак, класс 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/

2 голосов
/ 12 октября 2011

В вашей реализации нет ничего плохого. И, как мы все знаем, использование 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/

...