Я написал плагин JQuery я называю .thenst
. Это всего лишь then
, чтобы перейти к следующим вещам setTimeout
. Я не уверен, насколько он полезен для человечества в целом, но он решает эту проблему.
Вот демо на http://jsfiddle.net/ubershmekel/8mtbM/2/
Этот код плагин
(function ($) {
/*
Give deferred objects a `.thenst` function which chains like `then`
but adds a `setTimeout` in between to allow for UI responsiveness.
This is extra useful for chaining ~1-second long functions.
*/
var origDeferred = $.Deferred;
$.Deferred = function (func) {
var thenst = function (fnDone, fnFail, fnProgress) {
var newDef = $.Deferred();
var args;
var runsAfterSetTimeout = function() {
newDef.then(fnDone);
newDef.resolveWith(newDef, args);
};
def.then(function() {
args = arguments;
setTimeout(runsAfterSetTimeout, 0)
}, fnFail, fnProgress);
return newDef;
};
// Hooking jQuery's Deferred is hard because we have to modify Deferred()
// and Deferred.promise() though a Deferred has all the methods of a promise.
// It would be nicer if we could only extend the `promise ` object.
var def = origDeferred(func);
def.thenst = thenst;
var origPromise = def.promise;
def.promise = function (obj) {
obj = origPromise(obj);
obj.thenst = thenst;
return obj;
}
return def;
};
}(jQuery));
А вот пример страницы с примером его работы только в случае, если когда-либо jsfiddle выдыхается.
<!DOCTYPE html>
<html lang="en-us">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" type="text/javascript"></script>
</head>
<body>
<p>Notice how fillLinear/fillDefer don't let you see the numbers were cleared. The button doesn't become depressed. And you don't see the numbers updating as they're pinned. fillSetTimeout works well but is hard to read and write. The performance difference between fillSetTimeout and fillThenst is inconsisntent and is probably garbage-collection and redrawing-timing related.</p>
<button onclick="fillLinear()">fillLinear</button>
<button onclick="fillDefer()">fillDefer</button>
<button onclick="fillSetTimeout()">fillSetTimeout</button>
<button onclick="fillThenst()">fillThenst</button>
<div id="panel"></div>
<script>
(function ($) {
/*
Give deferred objects a `.thenst` function which chains like `then`
but adds a `setTimeout` in between to allow for UI responsiveness.
This is extra useful for chaining ~1-second long functions.
*/
var origDeferred = $.Deferred;
$.Deferred = function (func) {
var thenst = function(fnDone, fnFail, fnProgress) {
var newDef = $.Deferred();
var args;
var runsAfterSetTimeout = function() {
/*if ($.isFunction(fnDone)) {
// call the function and report it's done
fnDone.apply(newDef, args);
} else {
// fnDone should be a promise object like:
// $.Deferred().thenst($.ajax());
// We want to trigger that promise to run, I'm not sure if this is the way to do that.
// Though this point is kinda moot for $.ajax as it would fire off at instantiation.
newDef.then(fnDone);*/
newDef.then(fnDone);
newDef.resolveWith(newDef, args);
};
def.then(function() { args = arguments; setTimeout(runsAfterSetTimeout, 0) }, fnFail, fnProgress);
return newDef;
};
// Hooking jQuery's Deferred is hard because we have to modify Deferred()
// and Deferred.promise() though a Deferred has all the methods of a promise.
// It would be nicer if we could only extend the `promise ` object.
var def = origDeferred(func);
def.thenst = thenst;
var origPromise = def.promise;
def.promise = function(obj) {
obj = origPromise(obj);
obj.thenst = thenst;
return obj;
}
return def;
};
}(jQuery));
var setTimeoutDelay = 50; // I noticed that 0 releases the UI in IE10 but not fully in Chrome31 or FF25.
var time = function() {
return (new Date).getTime();
}
busy = function() {
var start = time();
while (time() - start < 1000) {
}
console.log('dn ' + time());
}
pin = function(i) {
$('#panel').append($('<h1>' + i + '</h1>'));
}
bclear = function() {
$('#panel').html('');
}
fillLinear = function() {
bclear();
pin(0);
busy();
pin(1);
busy();
pin(2);
busy();
pin(3);
}
fillDefer = function() {
bclear();
pin(0);
var def = $.Deferred();
def.then(function() {
busy();
pin(1);
}).then(function() {
busy();
pin(2);
}).then(function() {
busy();
pin(3);
});
def.resolve();
}
fillSetTimeout = function() {
bclear();
pin(0);
setTimeout(function() {
busy();
pin(1);
setTimeout(function() {
busy();
pin(2);
setTimeout(function() {
busy();
pin(3);
}, setTimeoutDelay);
}, setTimeoutDelay);
}, setTimeoutDelay);
}
fillThenst = function() {
bclear();
pin(0);
var def = $.Deferred();
def.thenst(function() {
busy();
pin(1);
}).thenst(function() {
busy();
pin(2);
}).thenst(function() {
busy();
pin(3);
});
def.resolve();
}
</script>
</body>
</html>
PS thenst означает then-setTimeout. Как неважно, как это звучит.
Причина fill3 позволяет продолжить рендеринг, вы используете setTimeout, который действует как асинхронный метод, так что он ждет несколько секунд, прежде чем продолжить. К сожалению, единственный способ повторить это с отсрочкой - также использовать setTimeout. Лучшее решение - исправить любое «занятое», чтобы оно не блокировало рендеринг. –
'busy' - это несколько длительных функций, загрузка файла, разбор его, а затем рекурсивное перемещение графика. Каждой из них может быть несколько секунд. – ubershmekel
Правильно, но это не должно блокировать рендеринг. Если это так, то его можно оптимизировать или модифицировать таким образом, чтобы он не блокировал рендеринг. –