This is an archived snapshot of W3C's public bugzilla bug tracker, decommissioned in April 2019. Please see the home page for more details.

Bug 15007 - Add an API to queue a task
Summary: Add an API to queue a task
Status: RESOLVED WONTFIX
Alias: None
Product: HTML WG
Classification: Unclassified
Component: HTML5 spec (show other bugs)
Version: unspecified
Hardware: PC All
: P2 normal
Target Milestone: ---
Assignee: Ian 'Hixie' Hickson
QA Contact: HTML WG Bugzilla archive list
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2011-11-30 14:02 UTC by Simon Pieters
Modified: 2011-12-09 22:03 UTC (History)
12 users (show)

See Also:


Attachments

Description Simon Pieters 2011-11-30 14:02:16 UTC
There are times when one wants to do something async. This is possible with setTimeout, but that doesn't run "as soon as possible". It's possible to abuse some API that uses async events, e.g. window.postMessage or XHR, but that's abuse. It would be nice if there were an API just to dispatch an event async i.e. put it on the event queue.
Comment 1 Anne 2011-11-30 14:07:49 UTC
setTimeout() with 0 is effectively that as far as I know.
Comment 2 Simon Pieters 2011-11-30 14:22:52 UTC
If it's a one-off, yes, but if you want to nest, then it's throttled at 4ms per spec.

See http://ajaxian.com/archives/settimeout-delay
Comment 3 Glenn Maynard 2011-11-30 16:28:03 UTC
Running things "async" (that is, "queue a task") is much more general than just firing events.  I don't think a special-case method just for "queue a task to fire an event" is the best solution; what we're missing is "queue a task".

setTimeout(0) is throttled largely due to legacy code that runs zero-second setTimeout loops, causing scripts to run endlessly if no throttling is imposed.  This happened because IE had an effective minimum timeout of 10ms, and code ended up assuming that.  (This is just for context; everyone reading knows this.)

I've suggested adding a new method which acts like setTimeout(0) with no throttling, for use cases like this.  It would have no throttling to begin with, so it won't have the legacy problems of setTimeout.  I don't think there was much interest, though.  (I don't remember where that discussion was.  Maybe I'm misremembering actually bringing this up before.)  It would simply queue a task to run the given callback, returning a long, like setTimeout, which can be passed to clearTimeout.
Comment 4 Simon Pieters 2011-12-01 08:35:17 UTC
(In reply to comment #3)
> what we're missing is "queue a task".

Yes.
Comment 5 Olli Pettay 2011-12-01 10:04:54 UTC
I would assume that in background windows/tabs browser should be able
to postpone firing such queue-a-task things, or otherwise websites
will just misuse it it, like they have misused setTimeout.
Comment 6 Glenn Maynard 2011-12-01 17:01:31 UTC
(In reply to comment #5)
> I would assume that in background windows/tabs browser should be able
> to postpone firing such queue-a-task things, or otherwise websites
> will just misuse it it, like they have misused setTimeout.

UAs do need to defend against busy looping with this API.  That should only happen if unreasonable looping is actually happening, though, just as browsers detect scripts running for too long.  Browsers shouldn't block these events in the background entirely.
Comment 7 Dominic Cooney 2011-12-02 06:55:31 UTC
FWIW the Mutation Observers spec has the concept of something that runs after the outermost JavaScript frame exits, but without the traditional setTimeout delay. I believe they call this a "microtask." It might be useful to have a single, consistent concept.
Comment 8 Glenn Maynard 2011-12-02 07:13:06 UTC
(In reply to comment #7)
> FWIW the Mutation Observers spec has the concept of something that runs after
> the outermost JavaScript frame exits, but without the traditional setTimeout
> delay. I believe they call this a "microtask." It might be useful to have a
> single, consistent concept.

Bear in mind that "queue a task" isn't something new we're inventing; it's a specific algorithm in the platform.  It's not something that could be merged with what you're describing.
Comment 9 Henri Sivonen 2011-12-02 08:44:15 UTC
(In reply to comment #0)
> There are times when one wants to do something async. This is possible with
> setTimeout, but that doesn't run "as soon as possible". It's possible to abuse
> some API that uses async events, e.g. window.postMessage or XHR, but that's
> abuse. It would be nice if there were an API just to dispatch an event async
> i.e. put it on the event queue.

Why is using postMessage to self abuse? (It would be nice to have some sugaring to do it without the boilerplate, though.)
Comment 10 Simon Pieters 2011-12-02 10:15:10 UTC
postMessage wasn't intended for that. Setting a listener for message on window means that other windows can invoke the listener, so the boilerplate needs to prevent that. Also, one might want to use postMessage for its intended purpose, too, which means both the zero-timeout boilerplate *and* the normal postMessage use need to filter the incoming messages.
Comment 11 Simon Pieters 2011-12-02 10:22:27 UTC
(In reply to comment #5)
> I would assume that in background windows/tabs browser should be able
> to postpone firing such queue-a-task things, or otherwise websites
> will just misuse it it, like they have misused setTimeout.

This isn't intended for driving animations. I can see a case for giving background tabs lower priority, but I don't think they should stop completely like requestAnimationFrame, or throttle to 1s like setTimeout.
Comment 12 Ian 'Hixie' Hickson 2011-12-02 16:07:15 UTC
How is this not setTimeout(task, 0)?
Comment 13 Glenn Maynard 2011-12-02 17:05:02 UTC
(In reply to comment #12)
> How is this not setTimeout(task, 0)?

(We've had that discussion before, I'll postpone rehashing it in favor of seeing other people's thoughts.)

(In reply to comment #11)
> This isn't intended for driving animations. I can see a case for giving
> background tabs lower priority, but I don't think they should stop completely
> like requestAnimationFrame, or throttle to 1s like setTimeout.

setTimeout throttles to 4ms, not one second, and only for nested timeouts (at least per spec).  But that's all just a workaround for legacy code--code tested in browsers that had a 10ms minimum timeout.
Comment 14 Glenn Maynard 2011-12-02 20:24:29 UTC
(In reply to comment #10)
> postMessage wasn't intended for that. Setting a listener for message on window
> means that other windows can invoke the listener, so the boilerplate needs to
> prevent that. Also, one might want to use postMessage for its intended purpose,
> too, which means both the zero-timeout boilerplate *and* the normal postMessage
> use need to filter the incoming messages.

However, I notice you can use MessageChannel for this purpose, without any of these problems.

var queueTask = function(task)
{
	var mc = new MessageChannel();
	mc.port1.onmessage = task;
	mc.port2.postMessage(null);
}

queueTask(function() { console.log("test"); });

In Chrome, this usually results in about a 10ms delay, but that's an implementation detail (you get that from most async callbacks).  It's not mandated by the spec, like the setTimeout minimum delay.

This isn't exactly the same as "queue a task", since the port message queue is separate from the task queue, so the function will be called in a different order relative to other tasks.  I doubt that's a problem.

I think I like this better than adding a new method, and it seems like a fairly natural use of MessageChannel.
Comment 15 Boris Zbarsky 2011-12-03 03:29:28 UTC
There has been a proposal from Microsoft for something like this.  They're calling it setImmediate.  That proposal is basically setTimeout without the clamp, for foreground tabs.  For background tabs there's talk of allowing clamping setImmediate.

The background tab story is very complicated.  Basically, battery considerations mean that UAs would like background tabs to use as little CPU as possible _and_ to have as few wakeups as possible.  This last is needed to allow the processor to actually go into low-power states.  We didn't completely turn off setTimeout in background tabs mostly because of compat worries, I think.  Any API _designed_ to be used to schedule rapid-fire tasks into the event loop would quite likely just be disabled altogether in background tabs, at least in Gecko....

That all said, I'd really like to understand what the use cases are here, especially for background tabs, before we start discussing details of what the API should look like.
Comment 16 Simon Pieters 2011-12-05 10:17:40 UTC
(In reply to comment #14)
> However, I notice you can use MessageChannel for this purpose, without any of
> these problems.
> 
> var queueTask = function(task)
> {
>     var mc = new MessageChannel();
>     mc.port1.onmessage = task;
>     mc.port2.postMessage(null);
> }
> 
> queueTask(function() { console.log("test"); });

Nice find! Improved version with support for passing arguments and correct 'this':

var queueTask = function(task)
{
    var mc = new MessageChannel();
    var args = [].slice.call(arguments, 1);
    mc.port1.onmessage = function(){ task.apply(task, args); };
    mc.port2.postMessage(null);
}
queueTask(function(arg) { console.log(arg, this) }, "test");

Not as convenient as having it natively, but certainly works for me.
Comment 17 Simon Pieters 2011-12-05 10:58:05 UTC
Further improved version that allows the browser to GC old MessageChannels:

var queueTask = function(task)
{
    var mc = new MessageChannel();
    var args = [].slice.call(arguments, 1);
    mc.port1.onmessage = function(){ this.onmessage = null; task.apply(task, args); };
    mc.port2.postMessage(null);
}
queueTask(function(arg) { console.log(arg, this) }, "test");
Comment 18 Glenn Maynard 2011-12-05 15:57:40 UTC
Clearing onmessage isn't necessary for GC.  Once no messages are in the message queue, the ports can be collected, since neither of the ports are reachable from live code.

We should disentangle the message ports, though:

var queueTask = function(task)
{
    var mc = new MessageChannel();
    var args = [].slice.call(arguments, 1);
    mc.port1.onmessage = function() { task.apply(task, args); };
    mc.port2.postMessage(null);
    mc.port2.close();
}
queueTask(function(arg) { console.log(arg, this) }, "test");

However, I notice a subtle problem: each MessageChannel acts as its own task source.  This means that messages sent using this function are not guaranteed to execute in order, since the order task sources are processed isn't specified.  Tasks are ordered only *within* a single task source.  Chrome just happens to do what we want.

We can work around that here by reusing a single MessageChannel:

var _taskChannel = new MessageChannel();
var _taskQueue = [];
_taskChannel.port1.onmessage = function()
{
    var task = _taskQueue[0][0];
    var args = _taskQueue[0][1];
    _taskQueue.shift();
    task.apply(task, args);
};

var queueTask = function(task)
{
    var args = [].slice.call(arguments, 1);
    _taskQueue.push([task, args]);
    _taskChannel.port2.postMessage(null);
}
queueTask(function(arg) { console.log(arg, this) }, "test1");
queueTask(function(arg) { console.log(arg, this) }, "test2");
queueTask(function(arg) { console.log(arg, this) }, "test3");
Comment 19 Simon Pieters 2011-12-05 16:46:31 UTC
(In reply to comment #18)
> However, I notice a subtle problem: each MessageChannel acts as its own task
> source.  This means that messages sent using this function are not guaranteed
> to execute in order, since the order task sources are processed isn't
> specified. 

https://www.w3.org/Bugs/Public/show_bug.cgi?id=15063

> Tasks are ordered only *within* a single task source.  Chrome just
> happens to do what we want.

Opera also.
Comment 20 Simon Pieters 2011-12-05 16:52:56 UTC
Given how apparently hard it is to get right, maybe it is better to have something like setImmediate natively anyway.

What this bug needs now is use cases. My recent needs for this aren't "real-world" enough (e.g. doing SRT research). Any Web developers want to comment? Maybe Microsoft have some use cases?
Comment 21 Boris Zbarsky 2011-12-05 17:04:23 UTC
I'd be interested in your non-real-world use case too.  How would it benefit from setImmediate?
Comment 22 Glenn Maynard 2011-12-05 17:11:38 UTC
I don't think it's very hard to get right (no harder than other MessageChannel uses), and it's a small amount of code.  I'm not sure pushing for a native method for this is worth it.
Comment 23 Simon Pieters 2011-12-05 17:37:44 UTC
(In reply to comment #21)
> I'd be interested in your non-real-world use case too.  How would it benefit
> from setImmediate?

It turns out that my SRT research didn't actually need this after all, since it used XHR and could have hooked in to its events instead.
Comment 24 Domenic Denicola 2011-12-06 15:38:38 UTC
setImmediate has many different possible implementations, depending on the browser. No need to reinvent them in Bugzilla comments ;).

See the implementations at https://github.com/NobleJS/setImmediate and follow the links in the readme there for some discussion and specs.
Comment 25 Glenn Maynard 2011-12-06 16:05:36 UTC
That implementation seems to have all of the MessageChannel-related bugs which we ironed out above (not disentangling the port; relying on the relative order of the separate channels' task queues), so I think this has been quite useful.
Comment 26 Domenic Denicola 2011-12-06 16:12:53 UTC
See lines 26--58 and relevant test cases.
Comment 27 Glenn Maynard 2011-12-06 16:22:51 UTC
I don't see anything there that would avoid the bugs I mentioned: the channel.port1.onmessage callbacks are not guaranteed to be called in order across distinct MessageChannels, and the lack of a "channel.close()" call may prevent the channel from being GCd (see the bottom note at http://dev.w3.org/html5/postmsg/#ports-and-garbage-collection).
Comment 28 Domenic Denicola 2011-12-06 16:32:29 UTC
While the missing close is indeed a bug, note how it spins until any previous invocations have completed, which I believe is all that the setImmediate spec requires (section 5 step 6). Order is not guaranteed. I believe the upthread implementation would fail that requirement actually, but can't be sure until I'm back at a computer.

Whether the spec should include a new clause demanding ordering, or remove the clause requiring task invocations to complete before queueing a new one, is another question.
Comment 29 Glenn Maynard 2011-12-06 22:41:02 UTC
That spec (https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/setImmediate/Overview.html) definitely requires that this code:

setImmediate(function() { console.log("x2"); });
setImmediate(function() { console.log("x3"); });
console.log("x1");

print "x1 x2 x3", and never "x1 x3 x2".  Step 6 of setImmediate ("Wait until any invocations ...") ensures that the tasks are queued in the order setImmediate is called.

Your code may print "x1 x3 x2", if the MessageChannel task queues happen to be run in that order.  To ensure correct ordering, you a single MessageChannel should be used for all calls, as was done above.

(I think there are fundamental issues with the "efficient script yielding" spec, and I doubt it'll see general adoption in that form, but it does get the task ordering right.)
Comment 30 Ian 'Hixie' Hickson 2011-12-07 00:01:50 UTC
"setTimeout() is throttled when nested" is not a reason to invent a new API here. Any API we invent here will eventually end up throttled.

Seems to me there's already several quite legitimate and simple ways to do this, as discussed above. I don't really see an argument for yet another new feature.
Comment 31 Simon Pieters 2011-12-07 07:57:13 UTC
(In reply to comment #30)
> "setTimeout() is throttled when nested" is not a reason to invent a new API
> here. Any API we invent here will eventually end up throttled.

We already have APIs all over that queue a task to fire an event, which can be used and is being used to implement polyfills for what is being requested in this bug. They are not throttled today, and I don't see why they would be in the future (throttling them would hurt perf benchmarks for one). setTimeout was throttled *from the beginning*, which is why it has to be today. An API that is not throttled from the beginning does not produce a legacy that expects throttling.

> Seems to me there's already several quite legitimate and simple ways to do
> this, as discussed above. I don't really see an argument for yet another new
> feature.

It's hard to implement a bug-free polyfill, as evidenced by the comments above.
Comment 32 Glenn Maynard 2011-12-07 15:25:28 UTC
(In reply to comment #31)
> It's hard to implement a bug-free polyfill, as evidenced by the comments above.

We implemented this in just a few minutes with MessageChannel, so I don't think this is true.  The fact that we didn't get it right on our very first pass doesn't mean it's hard to implement, and in any case people don't need to implement it themselves; they can use the above code.

To make it explicit: the parts of the above queueTask function written by me are in the public domain.

If code like this is made available in jQuery and Prototype, and ends up being used a lot, then maybe it's worth looking further into providing a native method, but right now I don't think it's needed.

FYI, note that MessageChannel does incur a delay in Chrome (~0-10ms), but this happens as far as I can see on *all* async callbacks in that browser, so I expect a native method for this would be no different.
Comment 33 Ian 'Hixie' Hickson 2011-12-09 22:03:35 UTC
EDITOR'S RESPONSE: This is an Editor's Response to your comment. If you are satisfied with this response, please change the state of this bug to CLOSED. If you have additional information and would like the editor to reconsider, please reopen this bug. If you would like to escalate the issue to the full HTML Working Group, please add the TrackerRequest keyword to this bug, and suggest title and text for the tracker issue; or you may create a tracker issue yourself, if you are able to do so. For more details, see this document:
   http://dev.w3.org/html5/decision-policy/decision-policy.html

Status: Rejected
Change Description: no spec change
Rationale: I don't think it's that hard to implement using MessageChannel, nor is that the only possible solution. In the bug above, a decent solution was developed by two people chatting in a bug in a matter of hours.

(BTW, I don't think the buzzword "polyfill" is being used correctly above. Isn't "polyfill" specifically about providing implementations of features for legacy UAs? I think what you're describing is just a library.)