requestIdleCallback
requestIdleCallback copied to clipboard
Support `options` parameter
Spec mentions second parameter for requestIdleCallback to support execution timeout:
https://w3c.github.io/requestidlecallback/#the-requestidlecallback-method
I don't think that can be supported. Browsers ensure that the callback is called within the timeout period, but without the native implementation you cannot force the browser to call the callback. At least I haven't found a way yet.
Can we polyfill it as close as possible?
Something like using setTimeout() and then clearTimeout() if it's called early.
These are the defers supported in browsers:
var global = require("./global");
var uniqueId = require("./uniqueId");
var polyfills = require("./polyfills");
var assert = require("./assert");
var supportedDefers = [];
if (!polyfills.isRegistered({Promise: true}))
supportedDefers.push(function (callback) {
Promise.resolve().then(callback);
});
if (global.MutationObserver)
supportedDefers.push(function (callback) {
var textNode = document.createTextNode("");
var observer = new MutationObserver(function () {
observer.disconnect();
callback();
});
observer.observe(textNode, {
characterData: true
});
textNode.data = "1";
});
if (global.requestIdleCallback)
supportedDefers.push(requestIdleCallback, function (callback) {
requestIdleCallback(callback, {timeout: 1});
});
supportedDefers.push(function (callback) {
setTimeout(callback, 0);
});
if (global.setImmediate)
supportedDefers.push(setImmediate);
if (global.requestAnimationFrame)
supportedDefers.push(requestAnimationFrame);
if (global.postMessage) {
var postMessageDefer = function (callback) {
var data = uniqueId();
window.addEventListener("message", function handle(event) {
if (event.source !== window || event.origin !== location.origin || event.data !== data)
return;
window.removeEventListener("message", handle);
event.stopPropagation();
callback();
});
postMessage(data, location.origin);
};
postMessageDefer.conflicts = ["messaging"];
supportedDefers.push(postMessageDefer);
}
if (global.MessageChannel)
supportedDefers.push(function (callback) {
var channel = new MessageChannel();
channel.port1.onmessage = function (e) {
callback();
};
channel.port2.postMessage(null);
});
if ("onreadystatechange" in document.createElement("script")) {
var scriptReadyStateDefer = function (callback) {
var body = document.body;
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "javascript:";
script.onreadystatechange = function () {
delete script.onreadystatechange;
body.removeChild(script);
callback();
};
body.appendChild(script);
};
scriptReadyStateDefer.conflicts = ["DOM"];
supportedDefers.push(scriptReadyStateDefer);
}
assert(supportedDefers.length > 0);
module.exports = supportedDefers;
Maybe you can add localstorage based defers too, I am not sure.

I identified 3 async groups based on when they are called by loading a page in a child window. I deferred from an unload event listener.
Some of them is called before anything happens to the child window. That is "async" in the table. Some of them is called when the document of the child window is in a loading readyState, but before the scripts of that document run. I call that group "loading". Others are called after the load event of the page fired, I call those "deferred". I guess these groups are the same as tasks, microtasks, etc. described here: https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/ , but I don't have time to read the whole article. What is clear that requestIdleCallback + timeout is in the "loading" group. To polyfill it, we need defers from the same group, so maybe polling with MessageChannel (or postMessage) and checking the elapsed time with the performance API or with new Date().getTime() can be a possible polyfill. But be aware that IE, Edge, PhantomJS and Safari have only 2 async groups, so by those all of these will be in the "deferred" group. Which means calling the callback can be deferred even with seconds in those browsers, while Chrome, Firefox, Opera will call it in time with maybe 20msec accuracy. Polling with something in the "async" group is not a solution, because it defers the navigation in my tests, so it won't run parallel with anything. So in IE like browsers there is no way to polyfill it properly, because they work differently than other browsers. I am not sure though whether a different page loading mechanism or a different tasking, microtasking mechanism are causing this. We should try other tasking - microtasking tests, but I don't know of any...
@inf3rno
I think you overcomplicate things here. rICs are not micro tasks and rIC callbacks are not called at any place in a frame like for example setTimeout (tasks) or Promise.resolve().then (micro tasks). About when to call rIC callbacks it is always an easy answer. You want to get right behind the time after the browser does the frame commit. Using requestAnimationFrame you get the time right before the frame commit. Using postMessage, Promise.resolve().then and other micro task scheduler won't get you easily jump over the browser frame commit, but a setTimeout with 0 does.
So a rIC callback should always be called like this:
requestAnimationFrame(() => {
setTimeout(ricCallback, 0);
});
The thing is that this library adds a throttle method in front of it and detects wether currently is a lot of going on then the throttle delay is longer or wether the browser/user doesn't do much then the delay is lower.
This delay is currently between 66ms - 130ms. And this is why I don't think we need a working timeout option, because normally you don't need rIC that is always below 130ms.
Of course additionally this lib makes sure, that if a lot of rICs are called and exceed the deadline (which is either 7 or 22) they are moved to the next frame.
@aFarkas Check my Opera and Chrome results in the table I attached. They are over 1 sec, some of them over 2.5 secs. If you compare them to the requestIdleCallback(cb, {timeout:1}) results, the latter one is always delayed at most for 20 msecs, nor for 1-3 secs... You won't be able to polyfill that with setTimeout 0 or requestAnimationFrame... On the other hand I don't really need a feature like this too, because I don't work with animations or with anything where msec timing is critical. Maybe others need it, I am not sure.