based code work in Firefox with a small timeout (works in Internet Explorer/Chrome)?
I have the following code which demonstrates the difference in calling a long-running function directly from an event trigger, vs. using setTimeout()
.
Intended behavior:
When the first button is pressed, it appears pressed, the calculation runs for several seconds, then when the calculation finishes, the button appears depressed again and the second column changes from "not calculating yet" to "calculation done". (I won't elaborate on why that is supposed to happen; it's explained in linked answer.)
When the second button is pressed, the button depresses immediately; the second column immediately changes to "calculating..." text. When the calculation finishes several seconds later, the second column changes from "calculating..." to "calculation done".
What actually happens:
This works perfectly in Chrome (both buttons behave as expected)
This works perfectly in Internet Explorer 8
This does NOT work in Firefox (v.25) as-is. Specifically, the second button behaves 100% as the first one.
Changing the timeout in setTimeout()
from 0
to 1
has no effect
Changing the timeout in setTimeout()
from 0
to 500
works
Which leaves me with a big conundrum.
According to the whole reason behind why setTimeout()
works whereas lack of one doesn't, the delay should have zero effect on how things work, since setTimeout()
's main purpose is to change the queuing order here, NOT to delay things .
So, why is it not working with delay 0 or 1 on Firefox, but works as expected with delay 500 (and works with any delay on Internet Explorer 8/Chrome)?
UPDATE: In addition to source code below, I also made a JSFiddle. But for some reason JSFiddle refuses to even load on my Internet Explorer 8, so for that testing, the code below is required.
UPDATE2: Someone raised the possibility of there being an issue with configuration setting dom.min_timeout_value
in Firefox. I have edited it from 4 to 0, restarted the browser, and nothing was fixed. It still fails with a timeout of 0 or 1 and succeeds with 500.
Here is my source code - I simply saved it to a HTML file on C: drive and opened in all three browsers:
<html><body>
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<table border=1>
<tr><td><button id='do'>Do long calc - bad status!</button></td>
<td><div id='status'>Not Calculating yet.</div></td></tr>
<tr><td><button id='do_ok'>Do long calc - good status!</button></td>
<td><div id='status_ok'>Not Calculating yet.</div></td></tr>
</table>
<script>
function long_running(status_div) {
var result = 0;
for (var i = 0; i < 1000; i++) {
for (var j = 0; j < 700; j++) {
for (var k = 0; k < 200; k++) {
result = result + i + j + k;
}
}
}
$(status_div).text('calclation done');
}
// Assign events to buttons
$('#do').on('click', function () {
$('#status').text('calculating....');
long_running('#status');
});
$('#do_ok').on('click', function () {
$('#status_ok').text('calculating....');
window.setTimeout(function (){ long_running('#status_ok') }, 0);
});
</script>
</body></html>
To test, you will need to change the nested loop boundaries to 300/100/100 for Internet Explorer 8; or to 1000/1000/500 for Chrome, due to different sensitivity of "this JS is taking too long" error coupled with JavaScript engine speed.
There is a copy of the current (Jun 28, 2016) implementation of window.setTimeout()
in Ubuntu.
As we can see, the timer gets inserted by this line of code:
nsAutoPtr<TimeoutInfo>* insertedInfo =
mTimeouts.InsertElementSorted(newInfo.forget(), GetAutoPtrComparator(mTimeouts));
Then a few lines below you have an if()
statement:
if (insertedInfo == mTimeouts.Elements() && !mRunningExpiredTimeouts) {
...
The insertedInfo == mTimeouts.Elements()
checks whether the timer that was just inserted already timed out. The following block does NOT execute the attached function, but the main loop will immediately notice that a timer timed out and thus it will skip the IDLE state (a yield of the CPU) that you are expecting.
This clearly (at least to me) explains the behavior you are experiencing. The rendering on the screen is another process (task/thread) and the CPU needs to be relinquished for that other process to get a chance to re-paint the screen. For that to happen, you need to wait long enough so your timer function does not get executed immediately and a yield happens.
As you've notice a pause of 500ms does the trick. You can probably use a smaller number, such as 50ms. Either way it is not going to guarantee that a yield happens, but chances are it will happen if the computer on which that code is running is not currently swamped (ie an anti-virus is not currently running full speed in the background...)
The complete SetTimeout()
function from Firefox:
(location of the file in the source: dom/workers/WorkerPrivate.cpp
)
int32_t
WorkerPrivate::SetTimeout(JSContext* aCx,
dom::Function* aHandler,
const nsAString& aStringHandler,
int32_t aTimeout,
const Sequence<JS::Value>& aArguments,
bool aIsInterval,
ErrorResult& aRv)
{
AssertIsOnWorkerThread();
const int32_t timerId = mNextTimeoutId++;
Status currentStatus;
{
MutexAutoLock lock(mMutex);
currentStatus = mStatus;
}
// It's a script bug if setTimeout/setInterval are called from a close handler
// so throw an exception.
if (currentStatus == Closing) {
JS_ReportError(aCx, "Cannot schedule timeouts from the close handler!");
}
// If the worker is trying to call setTimeout/setInterval and the parent
// thread has initiated the close process then just silently fail.
if (currentStatus >= Closing) {
aRv.Throw(NS_ERROR_FAILURE);
return 0;
}
nsAutoPtr<TimeoutInfo> newInfo(new TimeoutInfo());
newInfo->mIsInterval = aIsInterval;
newInfo->mId = timerId;
if (MOZ_UNLIKELY(timerId == INT32_MAX)) {
NS_WARNING("Timeout ids overflowed!");
mNextTimeoutId = 1;
}
// Take care of the main argument.
if (aHandler) {
newInfo->mTimeoutCallable = JS::ObjectValue(*aHandler->Callable());
}
else if (!aStringHandler.IsEmpty()) {
newInfo->mTimeoutString = aStringHandler;
}
else {
JS_ReportError(aCx, "Useless %s call (missing quotes around argument?)",
aIsInterval ? "setInterval" : "setTimeout");
return 0;
}
// See if any of the optional arguments were passed.
aTimeout = std::max(0, aTimeout);
newInfo->mInterval = TimeDuration::FromMilliseconds(aTimeout);
uint32_t argc = aArguments.Length();
if (argc && !newInfo->mTimeoutCallable.isUndefined()) {
nsTArray<JS::Heap<JS::Value>> extraArgVals(argc);
for (uint32_t index = 0; index < argc; index++) {
extraArgVals.AppendElement(aArguments[index]);
}
newInfo->mExtraArgVals.SwapElements(extraArgVals);
}
newInfo->mTargetTime = TimeStamp::Now() + newInfo->mInterval;
if (!newInfo->mTimeoutString.IsEmpty()) {
if (!nsJSUtils::GetCallingLocation(aCx, newInfo->mFilename, &newInfo->mLineNumber)) {
NS_WARNING("Failed to get calling location!");
}
}
nsAutoPtr<TimeoutInfo>* insertedInfo =
mTimeouts.InsertElementSorted(newInfo.forget(), GetAutoPtrComparator(mTimeouts));
LOG(TimeoutsLog(), ("Worker %p has new timeout: delay=%d interval=%sn",
this, aTimeout, aIsInterval ? "yes" : "no"));
// If the timeout we just made is set to fire next then we need to update the
// timer, unless we're currently running timeouts.
if (insertedInfo == mTimeouts.Elements() && !mRunningExpiredTimeouts) {
nsresult rv;
if (!mTimer) {
mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return 0;
}
mTimerRunnable = new TimerRunnable(this);
}
if (!mTimerRunning) {
if (!ModifyBusyCountFromWorker(true)) {
aRv.Throw(NS_ERROR_FAILURE);
return 0;
}
mTimerRunning = true;
}
if (!RescheduleTimeoutTimer(aCx)) {
aRv.Throw(NS_ERROR_FAILURE);
return 0;
}
}
return timerId;
}
IMPORTANT NOTE: The JavaScript instruction yield
, has nothing to do with what I am talking about. I am talking about the sched_yield() functionality which happens when a binary process calls certain functions, such as sched_yield()
itself, poll()
, select()
, etc.
I faced this issue with Firefox while toggling CSS classes using jQuery to control a CSS transition.
Increasing the duration of setTimeout to 50 from 0 helped, but as Alexis suggested this wasn't 100% reliable.
The best (if longwinded) solution I found was to combine an interval timer with an IF statement to actually check whether the necessary styles had been applied before triggering the transition, rather using setTimeout and assuming execution had taken place in the intended order, eg
var firefox_pause = setInterval(function() {
//Test whether page is ready for next step - in this case the div must have a max height applied
if ($('div').css('max-height') != "none") {
clear_firefox_pause();
//Add next step in queue here
}
}, 10);
function clear_firefox_pause() {
clearInterval(firefox_pause);
}
In my case at least, this seems to work every time in Firefox.
In Firefox, the minimum value for setTimeout() calls is configurable and defaults to 4 in current versions:
dom.min_timeout_value
The minimum length of time, in milliseconds, that the window.setTimeout() function can set a timeout delay for. This defaults to 4 ms (before 10 ms). Calls to setTimeout() with a delay smaller than this will be clamped to this minimum value.
Values like 0 or 1 should behave like 4—no idea if that will cause delays in your code or just break it.
链接地址: http://www.djcxy.com/p/59026.html上一篇: 给出'表达式结果不是函数'的setTimeout错误
下一篇: 基于浏览器的代码在Firefox中工作时间很短(适用于Internet Explorer / Chrome)?