jQuery: последовательно выполнять массив функций (как отложенных, так и не отложенных)

Я новичок в использовании промисов, и мне трудно понять отложенные jQuery.

В настоящее время у меня есть массив функций, которые я выполняю в определенный момент:

while (queue.length) {
    (queue.shift())();   
}

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

Таким образом, некоторые функции в очереди возвращают отложенное выполнение (например, через jQuery.ajax()), а некоторые являются просто обычными функциями. Я хотел бы знать, как можно было бы выполнить их по порядку + последовательно (выполнение следующей функции только после завершения предыдущей).

Я подумал, что, возможно, jQuery.when будет тем, что я ищу, но я не могу понять, как именно это сделать, я придумал только это:

var deferred = jQuery.Deferred();
    while (queue.length > 0) {
       deferred.done().then(queue.shift());
    }

person Riesling    schedule 24.07.2014    source источник
comment
если есть асинхронная функция.. тогда эти функции должны возвращать обещание или должны поддерживать механизм обратного вызова... только тогда вы можете это сделать   -  person Arun P Johny    schedule 24.07.2014
comment
асинхронные функции будут возвращать обещания, а другие нет   -  person Riesling    schedule 24.07.2014


Ответы (1)


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

var queue = [syncFunction, syncFunc2, returnsPromiseAsync];

var d = $.Deferred().resolve();
while (queue.length > 0) {
   d = d.then(queue.shift()); // you don't need the `.done`
}

(скрипка)

В качестве альтернативы вы можете уменьшить;

var res = queue.reduce(function(prev,cur){ // chain to res later to hook on done
    return prev.then(cur);
}, $.Deferred().resolve());

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

var p = $.Deferred();
var res = queue.reduce(function(prev,cur){ // chain to res later to hook on done
    return prev.then(cur);
}, p);
setTimeout(p.resolve.bind(p), 0);

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

Если вам нужно знать, когда процесс завершится, просто используйте обработчик .then для res:

res.then(function() {
    // All functions have now completed
});

Для таких ситуаций вот простая функция-оболочка, которая делает и то, и другое:

function clearQueue(q) {
    var p = $.Deferred();
    setTimeout(p.resolve.bind(p), 0);

    return q.reduce(function(prev,cur){ 
        return prev.then(cur);
    }, p);
}

Пример использования (fiddle):

clearQueue(queue).then(function() {
    console.log("All done");
});
console.log("This runs before any queued function starts");
person Benjamin Gruenbaum    schedule 24.07.2014
comment
Теперь это прекрасно. :-) - person T.J. Crowder; 24.07.2014
comment
@ Roamer-1888: Это похоже на правду в текущей реализации; Я не вижу, чтобы это было задокументировано, а вы? Но с учетом того, как функции передаются в коде на основе обещаний, кажется, что они вряд ли изменят его (реализация)... - person T.J. Crowder; 25.07.2014
comment
Я, конечно же, узнал об отсоединяемости .resolve и .reject, как они это называли, из официальной документации jQuery. Я больше не могу найти исходный текст, но помню, что это была более ранняя версия jQuery.Deferred() страница. Тем не менее, эта функция, по крайней мере, признана, если не задокументирована, в примерах отложенной обработки на сайте Learn.jQuery.com. Ищите такие фразы, как .then( defer.resolve, defer.reject ), setTimeout( defer.resolve, ...) и image.onerror = defer.reject. - person Roamer-1888; 25.07.2014