I'm drawing an image to a canvas element. I then have code that depends on this process to be finished. My code looks like this:
var myContext = myCanvasElement.getContext('2d'),
myImg = new Image();
myImg.onload = function() {
myContext.drawImage(containerImg, 0, 0, 300, 300);
};
myImg.src = "someImage.png";
So now, I would like to be notified when drawImage is done. I checked the spec but I couldn't find either an event or the possibility to pass a callback function. So far I just set a timeout, but this obviously is not very sustainable. How do you solve this problem?
Like almost all Javascript functions, drawImage is synchronous, i.e. it'll only return once it has actually done what it's supposed to do.
That said, what it's supposed to do, like most other DOM calls, is queue-up lists of things to be repainted next time the browser gets into the event loop.
There's no event you can specifically register to tell you when that is, since by the time any such event handler could be called, the repaint would have already happened.
Jef Claes explains it pretty well on his website:
Browsers load images asynchronously while scripts are already being
interpreted and executed. If the image isn't fully loaded the canvas
fails to render it.
Luckily this isn't hard to resolve. We just have to wait to start
drawing until we receive a callback from the image, notifying loading
has completed.
<script type="text/javascript">
window.addEventListener("load", draw, true);
function draw(){
var img = new Image();
img.src = "http://3.bp.blogspot.com/_0sKGHtXHSes/TPt5KD-xQDI/AAAAAAAAA0s/udx3iWAzUeo/s1600/aspnethomepageplusdevtools.PNG";
img.onload = function(){
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.drawImage(img, 0, 0);
};
}
You already have an event when the image loads, and you do one thing (draw). Why not do another and call the function that will do whatever it is you want done after drawImage? Literally just:
myImg.onload = function() {
myContext.drawImage(containerImg, 0, 0, 300, 300);
notify(); // guaranteed to be called after drawImage
};
drawImage() as any drawing method on the 2D canvas in itself is "mostly" synchronous.
You can assume that any code that needs a read-back of the pixels will have the updated pixels. Also, for drawImage in particular, you can even assume that the image will have been fully decoded "synchronously", which can take some time with big images.
Technically, in most modern configs the actual painting work will be deferred to the GPU, which implies some parallelization and some asynchronicity, but read-backs will wait for the GPU has done its work and lock the CPU for that time.
However the drawing on the canvas is only the first step of the full rendering of the canvas to the monitor.
The canvas then needs to go through the CSS compositor, where it will get painted along the rest of the page. This is what is deferred to the next rendering step.
alert() in Chrome does currently block the CSS compositor, and thus, even though the actual pixels of the canvas buffer have been updated, these changes haven't been reflected by the CSS compositor yet. (In Firefox alert() triggers a kind of "spin the event loop" which allows the CSS compositor to still kick in, even if the global tasks of the event loop are paused).
To hook to the CSS compositor, there is a requestPostAnimationFrame method that is being incubated, but apparently got dropped of Chrome experiments recently.
We can polyfill it using both requestAnimationFrame and a MessageEvent to hook to the next task as soon as possible (setTimeout is generally given less priority).
Now, even this requestPostAnimationFrame is only an event for when the browser's compositor kicked in, there is still some time before that image gets to the OS compositor and to the monitor (about a full V-Sync frame).
Some configuration of Chrome on Windows have access to a shortcut that allows the browser to talk directly to the OS compositor, and bypasses the CSS compositor. To enable this option, you can create your 2D context with the desynchhronized option set to true. However, this option is only supported in a few configurations.
Below is a demo of almost all this:
// requestPostAnimationFrame polyfill
if (typeof requestPostAnimationFrame !== "function") {
(() => {
const channel = new MessageChannel();
const callbacks = [];
let timestamp = 0;
let called = false;
let scheduled = false; // to make it work from rAF
let inRAF = false; // to make it work from rAF
channel.port2.onmessage = e => {
called = false;
const toCall = callbacks.slice();
callbacks.length = 0;
toCall.forEach(fn => {
try {
fn(timestamp);
} catch (e) {}
});
}
// We need to overwrite rAF to let us know we are inside an rAF callback
// as to avoid scheduling yet an other rAF, which would be one painting frame late
// We could have hooked an infinite loop on rAF, but this means
// forcing the document to be animated all the time
// which is bad for perfs
const rAF = globalThis.requestAnimationFrame;
globalThis.requestAnimationFrame = function(...args) {
if (!scheduled) {
scheduled = true;
rAF.call(globalThis, (time) => inRAF = time);
globalThis.requestPostAnimationFrame(() => {
scheduled = false;
inRAF = false;
});
}
rAF.apply(globalThis, args);
};
globalThis.requestPostAnimationFrame = function(callback) {
if (typeof callback !== "function") {
throw new TypeError("Argument 1 is not callable");
}
callbacks.push(callback);
if (!called) {
if (inRAF) {
timestamp = inRAF;
channel.port1.postMessage("");
} else {
requestAnimationFrame((time) => {
timestamp = time;
channel.port1.postMessage("");
});
}
called = true;
}
};
})();
}
// now the demo
// if the current browser can use desync 2D context
// let's try it there too
// (I couldn't test it myself, so let me know in comments)
const supportsDesyncContext = CanvasRenderingContext2D.prototype.getContextAttributes &&
document.createElement("canvas")
.getContext("2d", { desynchronized: true })
.getContextAttributes().desynchronized;
test(false);
if (supportsDesyncContext) {
setTimeout(() => test(true), 1000);
}
async function test(desync) {
const canvas = document.createElement("canvas");
document.body.append(canvas);
const ctx = canvas.getContext("2d", { desynchronized: desync });
const blob = await fetch("https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png")
.then((resp) => resp.ok && resp.blob());
const bitmap = await createImageBitmap(blob);
ctx.drawImage(bitmap, 0, 0, 300, 150);
// schedule our callback after rendering
requestPostAnimationFrame(() => {
alert("Right after CSS compositing");
});
// prove that we actually already painted on the canvas
// even if the CSS compositor hasn't kicked in yet
const pixelOnCanvas = ctx.getImageData(120,120,1,1).data;
alert("Before CSS compositing." + (desync ? " (desynchronized)": "") + "\nPixel on canvas: " + pixelOnCanvas);
}
The answer by #MikeGledhill (that got deleted) is essentially the beginning of the answer, though it could have explained it better, and browsers may not have all had the requestAnimationFrame API available at that time:
Painting of pixels happens in the next animation frame. This means that if you call drawImage, the screen pixels won't actually be updated at that time, but in the next animation frame.
There's no event for this.
But! We can use requestAnimationFrame to schedule a callback for the next frame before paint (display update) happens:
myImg.onload = function() {
myContext.drawImage(containerImg, 0, 0, 300, 300);
requestAnimationFrame(() => {
// This function will run in the next animation frame, *right before*
// the browser will update the pixels on the display (paint).
// To ensure that we run logic *after* the display has been
// updated, an option is to queue yet one more callback
// using setTimeout.
setTimeout(() => {
// At this point, the page rendering has been updated with the
// `drawImage` result (or a later frame's result, see below).
}, 0)
})
};
What is happening here:
The requestAnimtionFrame call schedules a function that will be called right before the browser updated display pixels. After this callback is completed, the browser will continue to synchronously update the display pixels in a following tick that is very similar to a microtask.
The "microtask"-like in which the browser updates the display, happens after your requestAnimationFrame callback, and happens after all user-created microtasks that a user creates in the callback using Promise.resolve().then() or an await statement. This means one cannot make deferred code fire immediately (synchronously) after the paint task happens.
The only way to guarantee that logic will fire after the next paint task, is to use setTimeout (or a postMessage trick) to queue a macrotask (not microtask) from an animation frame callback. A macrotask queued from a requestAnimationFrame callback will fire after all microtasks and microtask-likes, including the task that updates the pixels. The setTimeout (or postMessage) macrotask will not fire synchronously after animation frame microtasks.
This approach is not perfect though. Most of the time, the macrotask queued from setTimeout (and more likely with postMessage) will fire before the next animation frame and paint cycle. But, due to the specification of setTimeout (and postMessage), there is no guarantee that the delay will be exactly what we specify (0 in this example), and the browser is free to use heuristics and/or hard-coded values like 2ms to determine when is the soonest time to run a setTimeout (macrotask) callback.
Due to this non-guaranteed non-synchronous nature of macrotask scheduling, it is possible, though in practice unlikely, that your setTimeout (or postMessage) callback can fire not just after the current animation frame (and the paint cycle that updates the display), but after the next animation frame (and its paint task), meaning that a macrotask callback has a small chance firing too late for the frame you were targeting. This chance is reduced when using postMessage instead of setTimeout.
That being said, this sort of thing is probably something you should not do unless you're trying to write tests that capture painted pixels and compare them to expected results or something similar.
In general, you should schedule any drawing logic (f.e. ctx.drawImage()) using requestAnimationFrame, never rely on the actual timing of the paint update, and assume that the user will see what the browser APIs guarantee you've specified for them to see (the browsers have their own tests in place for ensuring their APIs work).
Finally, we don't know what your actual goal is. Most likely this answer may be irrelevant to that goal.
Here's the same example using the postMessage trick:
let messageKey = 0
myImg.onload = function() {
myContext.drawImage(containerImg, 0, 0, 300, 300);
requestAnimationFrame(() => {
// This function will run in the next animation frame, *right before*
// the browser will update the pixels on the display (paint).
const key = "Unique message key for after paint callback: "+ messageKey++
// To ensure that we run logic *after* the display has been
// updated, an option is to queue yet one more callback
// using postMessage.
const afterPaint = (event) => {
// Ignore interference from any other messaging in the app.
if (event.data != key) return
removeEventListener('message', afterPaint)
// At this point, the page rendering has been updated with the
// `drawImage` result (or a later frame's result, but
// more unlikely than with setTimeout, as per above).
}
addEventListener('message', afterPaint)
// Hack: send a message which arrives back to us in a
// following macrotask, more likely sooner than with
// setTimeout.
postMessage(key, '*')
})
};
Related
var con = document.getElementById('con');
con.onclick = function () {
Promise.resolve().then(function Promise1() {
con.textContent = 0;
// requestAnimationFrame(() => con.textContent = 0)
});
};
<div id="con">this is con</div>
Why this code does not trigger rendering after performing microtasks?
setTimeout(function setTimeout1() {
console.log('setTimeout1')
}, 0)
var channel = new MessageChannel();
channel.port1.onmessage = function onmessage1() {
console.log('postMessage');
Promise.resolve().then(function promise1() {
console.log('promise1');
})
};
channel.port2.postMessage(0);
setTimeout(function setTimeout2() {
console.log('setTimeout2')
}, 0);
console.log('sync');
Why postmessage is executed before timer?
Why this code does not trigger rendering after performing microtasks?
It does, otherwise you wouldn't see the text being updated...
Maybe you are not able to tell it from your dev tools?
This is probably because mouse events are now generally throttled to the screen-refresh rate, meaning that when the task dispatching the mouse event will run, you'd already be in a painting frame, this may be for an other reason (because to my knowledge, mousemove events are throttled this way, not click...).
So there, your Promise callback will get executed synchronously (with only the sixth step "set currentTask to null" in between), before the update the rendering steps kicks in, and all the dev tools will see is a normal painting frame, just like it was expecting.
So maybe, the dev tools won't show anything particular here, but given the broadness of your claim, it's quite hard to pin-point a particular reason, and this is just a theory of mine.
You can try to validate this theory by calling requestAnimationFrame from inside such an event and check if it did execute in the same event loop iteration:
onclick = (evt) => {
console.clear();
setTimeout( () => console.log( 'timeout' ), 0 );
requestAnimationFrame( () => console.log( 'rAF' ) );
};
Click anywhere<br>
If "rAF" gets logged before "timeout", the click event got handled in a painting frame.
For me it does quite often in Chrome, and only once in a while in Firefox, but in the mean time I know Chrome's rAF is broken... so this theory is quite weak.
Why postmessage is executed before timer?
That will depend on the User-Agent (browser) and on when this code is executed for this statement to hold true, and also of course for the reason why it does.
In Chrome, they set a minimum 1ms to the timeout value passed to setTimeout:
base::TimeDelta interval_milliseconds =
std::max(base::TimeDelta::FromMilliseconds(1), interval);
the message task has no timeout and will thus get queued immediately. So if no other task is to be processed, it will be the next one executed, long before the 1ms timeout resolves.
In Firefox, they treat tasks scheduled by setTimeout as low priority, when scheduled from the page load (that means that in Firefox, the message task would actually fire after the setTimeout one, if both are scheduled after the page load:
function test() {
setTimeout(function setTimeout1() {
console.log('setTimeout1')
}, 0)
var channel = new MessageChannel();
channel.port1.onmessage = function onmessage1() {
console.log('postMessage');
Promise.resolve().then(function promise1() {
console.log('promise1');
})
};
channel.port2.postMessage(0);
setTimeout(function setTimeout2() {
console.log('setTimeout2')
}, 0);
console.log('sync');
}
console.log( 'testing # page load' );
test();
setTimeout(() => {
console.log( 'testing after page load' );
test();
}, 1000 );
/* results in Firefox:
testing # page load
sync
postMessage
promise1
setTimeout1
setTimeout2
testing after page load
sync
setTimeout1
setTimeout2
postMessage
promise1
*/
).
So there, in this particular case of a page load, they will treat the message task as more important than the timeout one, and when the task executor will have to choose which task to execute next (as part of the first step of the Event Loop processing model), it will pick the message over the timeout.
But these are implementation quirks, and nothing in the specs does formalize this behavior.
JavaScript is a single threaded language and therefore it executes one command at a time. Asynchronous programming is being implemented via Web APIs (DOM for event handling, XMLHttpRequest for AJAX calls, WindowTimers for setTimeout) and the Event queue which are managed by the browser. So far, so good! Consider now, the following very simple code:
$('#mybox').hide(17000);
console.log('Previous command has not yet terminated!');
...
Could someone please explain to me the underlying mechanism of the above? Since .hide() has not yet finished (the animation lasts 17 seconds) and JS engine is dealing with it and it is capable of executing one command at a time, in which way does it go to the next line and continues to run the remaining code?
If your answer is that animation creates promises, the question remains the same: How JavaScript is dealing with more than one thing at the same time (executing the animation itself, watching the animation queue in case of promises and proceeding with the code that follows...).
Moreover, I cannot explain how promises in jQuery work if they have to watch their parent Deferred object till it is resolved or rejected that means code execution and at the same time the remaining code is executed. How is that possible in a single threaded approach? I have no problem to understand AJAX calls for I know they are taken away from JS engine...
tl;dr; it would not be possible in a strictly single threaded environment without outside help.
I think I understand your issue. Let's get a few things out of the way:
JavaScript is always synchronous
No asynchronous APIs are defined in the language specification. All the functions like Array.prototype.map or String.fromCharCode always run synchronously*.
Code will always run to completion. Code does not stop running until it is terminated by a return, an implicit return (reaching the end of the code) or a throw (abruptly).
a();
b();
c();
d(); // the order of these functions executed is always a, b, c, d and nothing else will
// happen until all of them finish executing
JavaScript lives inside a platform
The JavaScript language defines a concept called a host environment:
In this way, the existing system is said to provide a host environment of objects and facilities, which completes the capabilities of the scripting language.
The host environment in which JavaScript is run in the browser is called the DOM or document object model. It specifies how your browser window interacts with the JavaScript language. In NodeJS for example the host environment is entirely different.
While all JavaScript objects and functions run synchronously to completion - the host environment may expose functions of its own which are not necessarily defined in JavaScript. They do not have the same restrictions standard JavaScript code has and may define different behaviors - for example the result of document.getElementsByClassName is a live DOM NodeList which has very different behavior from your ordinary JavaScript code:
var els = document.getElementsByClassName("foo");
var n = document.createElement("div");
n.className = "foo";
document.body.appendChild(n);
els.length; // this increased in 1, it keeps track of the elements on the page
// it behaves differently from a JavaScript array for example.
Some of these host functions have to perform I/O operations like schedule timers, perform network requests or perform file access. These APIs like all the other APIs have to run to completion. These APIs are by the host platform - they invoke capabilities your code doesn't have - typically (but not necessarily) they're written in C++ and use threading and operating system facilities for running things concurrently and in parallel. This concurrency can be just background work (like scheduling a timer) or actual parallelism (like WebWorkers - again part of the DOM and not JavaScript).
So, when you invoke actions on the DOM like setTimeout, or applying a class that causes CSS animation it is not bound to the same requirements your code has. It can use threading or operating system async io.
When you do something like:
setTimeout(function() {
console.log("World");
});
console.log("Hello");
What actually happens is:
The host function setTimeout is called with a parameter of type function. It pushes the function into a queue in the host environment.
the console.log("Hello") is executed synchronously.
All other synchronous code is run (note, the setTimeout call was completely synchronous here).
JavaScript finished running - control is transferred to the host environment.
The host environment notices it has something in the timers queue and enough time has passed so it calls its argument (the function) - console.log("World") is executed.
All other code in the function is run synchronously.
Control is yielded back to the host environment (platform).
Something else happens in the host environment (mouse click, AJAX request returning, timer firing). The host environment calls the handler the user passed to these actions.
Again all JavaScript is run synchronously.
And so on and so on...
Your specific case
$('#mybox').hide(17000);
console.log('Previous command has not yet terminated!');
Here the code is run synchronously. The previous command has terminated, but it did not actually do much - instead it scheduled a callback on the platform a(in the .hide(17000) and then executed the console.log since again - all JavaScirpt code runs synchronously always.
That is - hide performs very little work and runs for a few milliseconds and then schedules more work to be done later. It does not run for 17 seconds.
Now the implementation of hide looks something like:
function hide(element, howLong) {
var o = 16 / howLong; // calculate how much opacity to reduce each time
// ask the host environment to call us every 16ms
var t = setInterval(function
// make the element a little more transparent
element.style.opacity = (parseInt(element.style.opacity) || 1) - o;
if(parseInt(element.style.opacity) < o) { // last step
clearInterval(t); // ask the platform to stop calling us
o.style.display = "none"; // mark the element as hidden
}
,16);
}
So basically our code is single threaded - it asks the platform to call it 60 times a second and makes the element a little less visible each time. Everything is always run to completion but except for the first code execution the platform code (the host environment) is calling our code except for vice versa.
So the actual straightforward answer to your question is that the timing of the computation is "taken away" from your code much like in when you make an AJAX request. To answer it directly:
It would not be possible in a single threaded environment without help from outside.
That outside is the enclosing system that uses either threads or operating system asynchronous facilities - our host environment. It could not be done without it in pure standard ECMAScript.
* With the ES2015 inclusion of promises, the language delegates tasks back to the platform (host environment) - but that's an exception.
You have several kind of functions in javascript:
Blocking and non blocking.
Non blocking function will return immediately and the event loop continues execution while it work in background waiting to call the callback function (like Ajax promises).
Animation relies on setInterval and/or setTimeout and these two methods return immediately allowing code to resume. The callback is pushed back into the event loop stack, executed, and the main loop continues.
Hope this'll help.
You can have more information here or here
Event Loop
JavaScript uses what is called an event loop. The event loop is like a while(true) loop.
To simplify it, assume that JavaScript has one gigantic array where it stores all the events. The event loop loops through this event loop, starting from the oldest event to the newest event. That is, JavaScript does something like this:
while (true) {
var event = eventsArray.unshift();
if (event) {
event.process();
}
}
If, during the processing of the event (event.process), a new event is fired (let's call this eventA), the new event is saved in the eventsArray and execution of the current continues. When the current event is done processing, the next event is processed and so on, until we reach eventA.
Coming to your sample code,
$('#mybox').hide(17000);
console.log('Previous command has not yet terminated!');
When the first line is executed, an event listener is created and a timer is started. Say jQuery uses 100ms frames. A timer of 100ms is created, with a callback function. The timer starts running in the background (the implementation of this is internal to the browser), while the control is given back to your script. So, while the timer is running in the background, your script continues to line two. After 100ms, the timer finishes, and fires an event. This event is saved in the eventsArray above, it does not get executed immediately. Once your code is done executing, JavaScript checks the eventsArray and sees that there is one new event, and then executes it.
The event is then run, and your div or whatever element it is moves a few pixels, and a new 100ms timer starts.
Please note that this is a simplification, not the actual working of the whole thing. There are a few complications to the whole thing, like the stack and all. Please see the MDN article here for more info.
Could someone please explain to me the underlying mechanism of the
above? Since .hide() has not yet finished (the animation lasts 17
seconds) and JS engine is dealing with it and it is capable of
executing one command at a time, in which way does it go to the next
line and continues to run the remaining code?
jQuery.fn.hide() internally calls jQuery.fn.animate which calls jQuery.Animation which returns a jQuery deferred.promise() object; see also jQuery.Deferred()
The deferred.promise() method allows an asynchronous function to
prevent other code from interfering with the progress or status of its
internal request.
For description of Promise see Promises/A+ , promises-unwrapping , Basic Javascript promise implementation attempt ; also , What is Node.js?
jQuery.fn.hide:
function (speed, easing, callback) {
return speed == null || typeof speed === "boolean"
? cssFn.apply(this, arguments)
: this.animate(genFx(name, true), speed, easing, callback);
}
jQuery.fn.animate:
function animate(prop, speed, easing, callback) {
var empty = jQuery.isEmptyObject(prop),
optall = jQuery.speed(speed, easing, callback),
doAnimation = function () {
// Operate on a copy of prop so per-property easing won't be lost
var anim = Animation(this, jQuery.extend({},
prop), optall);
// Empty animations, or finishing resolves immediately
if (empty || jQuery._data(this, "finish")) {
anim.stop(true);
}
};
doAnimation.finish = doAnimation;
return empty || optall.queue === false ? this.each(doAnimation) : this.queue(optall.queue, doAnimation);
}
jQuery.Animation:
function Animation(elem, properties, options) {
var result, stopped, index = 0,
length = animationPrefilters.length,
deferred = jQuery.Deferred().always(function () {
// don't match elem in the :animated selector
delete tick.elem;
}),
tick = function () {
if (stopped) {
return false;
}
var currentTime = fxNow || createFxNow(),
remaining = Math.max(0, animation.startTime + animation.duration - currentTime),
// archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
temp = remaining / animation.duration || 0,
percent = 1 - temp,
index = 0,
length = animation.tweens.length;
for (; index < length; index++) {
animation.tweens[index].run(percent);
}
deferred.notifyWith(elem, [animation, percent, remaining]);
if (percent < 1 && length) {
return remaining;
} else {
deferred.resolveWith(elem, [animation]);
return false;
}
},
animation = deferred.promise({
elem: elem,
props: jQuery.extend({},
properties),
opts: jQuery.extend(true, {
specialEasing: {}
},
options),
originalProperties: properties,
originalOptions: options,
startTime: fxNow || createFxNow(),
duration: options.duration,
tweens: [],
createTween: function (prop, end) {
var tween = jQuery.Tween(elem, animation.opts, prop, end, animation.opts.specialEasing[prop] || animation.opts.easing);
animation.tweens.push(tween);
return tween;
},
stop: function (gotoEnd) {
var index = 0,
// if we are going to the end, we want to run all the tweens
// otherwise we skip this part
length = gotoEnd ? animation.tweens.length : 0;
if (stopped) {
return this;
}
stopped = true;
for (; index < length; index++) {
animation.tweens[index].run(1);
}
// resolve when we played the last frame
// otherwise, reject
if (gotoEnd) {
deferred.resolveWith(elem, [animation, gotoEnd]);
} else {
deferred.rejectWith(elem, [animation, gotoEnd]);
}
return this;
}
}),
props = animation.props;
propFilter(props, animation.opts.specialEasing);
for (; index < length; index++) {
result = animationPrefilters[index].call(animation, elem, props, animation.opts);
if (result) {
return result;
}
}
jQuery.map(props, createTween, animation);
if (jQuery.isFunction(animation.opts.start)) {
animation.opts.start.call(elem, animation);
}
jQuery.fx.timer(
jQuery.extend(tick, {
elem: elem,
anim: animation,
queue: animation.opts.queue
}));
// attach callbacks from options
return animation.progress(animation.opts.progress).done(animation.opts.done, animation.opts.complete).fail(animation.opts.fail).always(animation.opts.always);
}
When .hide() is called , a jQuery.Deferred() is created that processes the animation tasks.
This is the reason console.log() is called.
If include start option of .hide() can review that .hide() begins before console.log() is called on next line, though does not block the user interface from performing asynchronous tasks.
$("#mybox").hide({
duration:17000,
start:function() {
console.log("start function of .hide()");
}
});
console.log("Previous command has not yet terminated!");
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js">
</script>
<div id="mybox">mybox</div>
Native Promise implementation
function init() {
function $(id) {
return document.getElementById(id.slice(1))
}
function hide(duration, start) {
element = this;
var height = parseInt(window.getComputedStyle(element)
.getPropertyValue("height"));
console.log("hide() start, height", height);
var promise = new Promise(function(resolve, reject) {
var fx = height / duration;
var start = null;
function step(timestamp) {
if (!start) start = timestamp;
var progress = timestamp - start;
height = height - fx * 20.5;
element.style.height = height + "px";
console.log(height, progress);
if (progress < duration || height > 0) {
window.requestAnimationFrame(step);
} else {
resolve(element);
}
}
window.requestAnimationFrame(step);
});
return promise.then(function(el) {
console.log("hide() end, height", height);
el.innerHTML = "animation complete";
return el
})
}
hide.call($("#mybox"), 17000);
console.log("Previous command has not yet terminated!");
}
window.addEventListener("load", init)
#mybox {
position: relative;
height:200px;
background: blue;
}
<div id="mybox"></div>
I want to call a javascript function that takes a long time (5 seconds) to finish without freezing my client's browser. They click the button to start the operation, then they should be notified when it finishes. Currently I have written something like
$(document).ready(function(){
$('#tokenbutton').click(function(){
// IMMEDIATE EFFECT
$('#tokenbutton').attr("disabled",true).val("Computing...");
var callback = function(resultsFromExpensiveOperation){
// CALLBACK EFFECTS
$('#tokenbutton').val("Token computed");
$('#submitbutton').attr("disabled",false);
// ...
};
// FREEZES BROWSER
doExpensiveOperation(callback);
});
});
and
doExpensiveOperation = function(callback){
// ...
//var results = ...
callback(results);
};
but this freezes my browser when I run it. How can I change it so this doesn't freeze my browser?
There are no real asynchronous is javascript, thus no multi thread or anything like that. There is a way to make long running function not freeze the browser in certain cases though.
You can use the setInterval or setTimeout function to do small bits of your long running task at a time, thus each bit takes a fraction of a second and then the ui becomes responsive again for a fraction of a second until the next bit runs. This functionally makes the ui stay responsive and does not add much time (if any) onto the processing of the code. For example.
long running code
function doSomething(){
for(var x = 0; x < 10000; x++){
// do something
}
}
// doing it like this could take forever and lock up the browser
broken up code
var total = 0;
setTimeout(doSomething, 4);
function doSomething(){
for(total = total; total + 100 < 100; total++){
// do something
}
if(total < 10000){
setTimeout(doSomething, 4);
}
}
// doing it like this stops the browser from freezing but does not save any time.
A few things,
I put a time of 4 ms into the setTimout because that is actually the lowest value js will except, even if you put 1 it defaults to 4.
I used a setTimeout pattern instead of a setInterval to prevent the next interval from running before the previous on finishes.
And lastly this pattern does not work for everything. It lends itself best to loop and sequence based operations.
Hope it helps
Use Case
Create a non-Ajax separate process that returns data to a callback.
Solution
Requirements
Browser must:
Support HTML5
Support Web Workers
Structure
Create a separate file for your separate threaded process with the logic. This file must contain the following code:
postMessage( //data )
Code
if(typeof(Worker) === "function") {
var worker = new Worker(//uri to js file);
worker.onmessage = function(event){
// my call back.
};
}
I have such a function in my JS script:
function heavyWork(){
for (i=0; i<300; i++){
doSomethingHeavy(i);
}
}
Maybe "doSomethingHeavy" is ok by itself, but repeating it 300 times causes the browser window to be stuck for a non-negligible time. In Chrome it's not that big of a problem because only one Tab is effected; but for Firefox its a complete disaster.
Is there any way to tell the browser/JS to "take it easy" and not block everything between calls to doSomethingHeavy?
You could nest your calls inside a setTimeout call:
for(...) {
setTimeout(function(i) {
return function() { doSomethingHeavy(i); }
}(i), 0);
}
This queues up calls to doSomethingHeavy for immediate execution, but other JavaScript operations can be wedged in between them.
A better solution is to actually have the browser spawn a new non-blocking process via Web Workers, but that's HTML5-specific.
EDIT:
Using setTimeout(fn, 0) actually takes much longer than zero milliseconds -- Firefox, for example, enforces a minimum 4-millisecond wait time. A better approach might be to use setZeroTimeout, which prefers postMessage for instantaneous, interrupt-able function invocation, but use setTimeout as a fallback for older browsers.
You can try wrapping each function call in a setTimeout, with a timeout of 0. This will push the calls to the bottom of the stack, and should let the browser rest between each one.
function heavyWork(){
for (i=0; i<300; i++){
setTimeout(function(){
doSomethingHeavy(i);
}, 0);
}
}
EDIT: I just realized this won't work. The i value will be the same for each loop iteration, you need to make a closure.
function heavyWork(){
for (i=0; i<300; i++){
setTimeout((function(x){
return function(){
doSomethingHeavy(x);
};
})(i), 0);
}
}
You need to use Web Workers
https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
There are a lot of links on web workers if you search around on google
We need to release control to the browser every so often to avoid monopolizing the browser's attention.
One way to release control is to use a setTimeout, which schedules a "callback" to be called at some period of time. For example:
var f1 = function() {
document.body.appendChild(document.createTextNode("Hello"));
setTimeout(f2, 1000);
};
var f2 = function() {
document.body.appendChild(document.createTextNode("World"));
};
Calling f1 here will add the word hello to your document, schedule a pending computation, and then release control to the browser. Eventually, f2 will be called.
Note that it's not enough to sprinkle setTimeout indiscriminately throughout your program as if it were magic pixie dust: you really need to encapsulate the rest of the computation in the callback. Typically, the setTimeout will be the last thing in a function, with the rest of the computation stuffed into the callback.
For your particular case, the code needs to be transformed carefully to something like this:
var heavyWork = function(i, onSuccess) {
if (i < 300) {
var restOfComputation = function() {
return heavyWork(i+1, onSuccess);
}
return doSomethingHeavy(i, restOfComputation);
} else {
onSuccess();
}
};
var restOfComputation = function(i, callback) {
// ... do some work, followed by:
setTimeout(callback, 0);
};
which will release control to the browser on every restOfComputation.
As another concrete example of this, see: How can I queue a series of sound HTML5 <audio> sound clips to play in sequence?
Advanced JavaScript programmers need to know how to do this program transformation or else they hit the problems that you're encountering. You'll find that if you use this technique, you'll have to write your programs in a peculiar style, where each function that can release control takes in a callback function. The technical term for this style is "continuation passing style" or "asynchronous style".
You can make many things:
optimize the loops - if the heavy works has something to do with DOM access see this answer
if the function is working with some kind of raw data use typed arrays MSDN MDN
the method with setTimeout() is called eteration. Very usefull.
the function seems to be very straight forward typicall for non-functional programming languages. JavaScript gains advantage of callbacks SO question.
one new feature is web workers MDN MSDN wikipedia.
the last thing ( maybe ) is to combine all the methods - with the traditional way the function is using only one thread. If you can use the web workers, you can divide the work between several. This should minimize the time needed to finish the task.
I see two ways:
a) You are allowed to use Html5 feature. Then you may consider to use a worker thread.
b) You split this task and queue a message which just do one call at once and iterating as long there is something to do.
There was a person that wrote a specific backgroundtask javascript library to do such heavy work.. you might check it out at this question here:
Execute Background Task In Javascript
Haven't used that for myself, just used the also mentioned thread usage.
function doSomethingHeavy(param){
if (param && param%100==0)
alert(param);
}
(function heavyWork(){
for (var i=0; i<=300; i++){
window.setTimeout(
(function(i){ return function(){doSomethingHeavy(i)}; })(i)
,0);
}
}())
There is a feature called requestIdleCallback (pretty recently adopted by most larger platforms) where you can run a function that will only execute when no other function takes up the event loop, which means for less important heavy work you can execute it safely without ever impacting the main thread (given that the task takes less than 16ms, which is one frame. Otherwise work has to be batched)
I wrote a function to execute a list of actions without impacting main thread. You can also pass a shouldCancel callback to cancel the workflow at any time. It will fallback to setTimeout:
export const idleWork = async (
actions: (() => void)[],
shouldCancel: () => boolean
): Promise<boolean> => {
const actionsCopied = [...actions];
const isRequestIdleCallbackAvailable = "requestIdleCallback" in window;
const promise = new Promise<boolean>((resolve) => {
if (isRequestIdleCallbackAvailable) {
const doWork: IdleRequestCallback = (deadline) => {
while (deadline.timeRemaining() > 0 && actionsCopied.length > 0) {
actionsCopied.shift()?.();
}
if (shouldCancel()) {
resolve(false);
}
if (actionsCopied.length > 0) {
window.requestIdleCallback(doWork, { timeout: 150 });
} else {
resolve(true);
}
};
window.requestIdleCallback(doWork, { timeout: 200 });
} else {
const doWork = () => {
actionsCopied.shift()?.();
if (shouldCancel()) {
resolve(false);
}
if (actionsCopied.length !== 0) {
setTimeout(doWork);
} else {
resolve(true);
}
};
setTimeout(doWork);
}
});
const isSuccessful = await promise;
return isSuccessful;
};
The above will execute a list of functions. The list can be extremely long and expensive, but as long as every individual task is under 16ms it will not impact main thread. Warning because not all browsers supports this yet, but webkit does
I'd like to be able to dispatch a bunch of work via JavaScript to be done in the browser in such a way that the browser stays responsive throughout.
The approach I'm trying to take is to chunk up the work, passing each chunk to a function that is then queued with a setTimeout(func, 0) call.
I need to know when all the work is done, so I'm storing the returned timer ID in a map (id -> true|false). This mapping is set to false in the next block of code after I have the timer ID, and the queued function sets the mapping to true when it completes... except, of course, the queued function doesn't know its timer ID.
Maybe there's a better/easier way... or some advice on how I can manipulate my map as I need to?
I would queue the work in an array, use one timeout to process the queue and call a callback once the queue is empty. Something like:
var work = [...];
var run = function(work, callback) {
setTimeout(function() {
if(work.length > 0) {
process(work.shift());
setTimeout(arguments.callee, 25);
}
else {
callback();
}
}, 25);
};
run(work, function() {
alert('Work is done!');
});
As JavaScript in browsers is single threaded there is no real advantage to run multiple timeouts (at least I think this is what you are doing). It may even slow down the browser.
I'd like to add that although javascript is single threaded you can still have multiple ajax calls going at once. I recently had a site that needed to do potentially hundreds of ajax calls and the browser just couldn't handle it. I created a queue that used setTimeOut to run 5 calls at once. When one of the ajax calls returned it fired a callback (which is handled by a single thread) and then made the next call on the stack.
Imagine you're a manager that can only talk to one person at a time, you give 5 employees assignments, then wait for their responses, which may come in any order. Once the first employee comes back and gives you the information, you give them a new assignment and wait for the next employee (or perhaps even the same employee) to come back. So although you're "single threaded" 5 things are going on at once.
There is an example right in the HTML Standard, how it is best to handle it:
To run tasks of several milliseconds back to back without any delay,
while still yielding back to the browser to avoid starving the user
interface (and to avoid the browser killing the script for hogging the
CPU), simply queue the next timer before performing work:
function doExpensiveWork() {
var done = false;
// ...
// this part of the function takes up to five milliseconds
// set done to true if we're done
// ...
return done;
}
function rescheduleWork() {
var handle = setTimeout(rescheduleWork, 0); // preschedule next iteration
if (doExpensiveWork())
clearTimeout(handle); // clear the timeout if we don't need it
}
function scheduleWork() {
setTimeout(rescheduleWork, 0);
}
scheduleWork(); // queues a task to do lots of work
The moment of finishing the work is pretty clear, when clearTimeout is called.