gloo
gloo copied to clipboard
Standard future factories for queueing actions at various points.
Summary
Note: I've written this using futures 0.3, but this is something that one could change.
Provide a set of async functions with the following signature
fn example() -> impl Future<Output = ()>
where the futures resolves in the following places
- on the next tick of the microtask queue (
Promise.then
) - at the end of the task queue (i.e. after scheduled repaints) (
setTimeout(0)
) - when the browser thinks we should update an animation (
requestAnimationFrame
) - after a certain amount of time, using
setTimeout
- possibly more places
This post about Tasks, microtasks etc. is how I learnt about all this.
Motivation
This library would serve 2 purposes.
It would make it easy to select the right time to do your operation, and it also educates the user of the library about the intricacies of the JavaScript event loop. I think grouping all these together because they are all futures that resolve to nothing at some point make them much easier to understand than how they are arranged in JavaScript (scattered everywhere).
Detailed Explanation
I would envisage the functions looking like
/// This future will resolve after current execution has finished, but before the browser does
/// anything else (e.g. screen repaints). This is the fastest choice.
fn next_microtask() -> impl Future<Output = ()>;
/// This future will resolve after the browser has finished the current execution, and after any
/// other scheduled events (e.g. screen repaints)
fn next_task() -> impl Future<Output = ()>;
/// This future will resolve when the browser determines that another frame of an animation
/// should be drawn. This will be in general slower than `next_task`, as the browser can process
/// events faster than the screen will refresh.
fn next_animation_frame() -> impl Future<Output = ()>;
/// This future will resolve in the future, at least `ms` milliseconds in the future. If you set `ms`
/// to `0`, then this future behaves like `next_task`.
///
/// # Panics
///
/// This function will panic if `ms` is greater than the maximum safe JavaScript integer.
/// Fortunately, this number is so big that waiting for the future to resolve would take 285
/// thousand years.
fn next_ms(ms: u64) -> impl Future<Output = ()>;
Drawbacks, Rationale, and Alternatives
The main drawback is that this is different to the way that JavaScript itself handles all these things. The alternative is to not do it.
Unresolved Questions
Callbacks are executed before promises, so in theory there could be another function next_callback
that executes before next_microtask
, but since they both execute before any browser events are handled, I think that would be overkill.
The setTimeout
wrapper could not panic, instead using the nearest f64. That method could also take a Duration
.
EDIT Formatting
after a certain amount of time, using setTimeout
That's already handled by the gloo-timers crate. In fact, I think all of these would fit in pretty well in gloo-timers.
The setTimeout wrapper could not panic, instead using the nearest f64.
I don't think saturating behavior is good.
Also, it should be u32
, not u64
, because the biggest ms for setTimeout
is 2147483647
(which is std::i32::MAX
)
This future will resolve after the browser has finished the current execution, and after any other scheduled events (e.g. screen repaints)
I don't think it's guaranteed that macrotasks execute after everything else. That is especially true with screen repaints, which are only guaranteed after requestAnimationFrame
.
The ordering is also not guaranteed (the browser can rearrange macrotask events). The only guarantee is that it will execute after all of the microtasks.
next_animation_frame
This should return f64
, not ()
.
We should also have an fn animation_frames() -> AnimationFrames
, where AnimationFrames
is a Stream<Output = f64>
, making it easy to create animation loops.
As a meta note, we've changed the proposal process so that RFCs must now be made as pull requests, because this makes them a lot easier to review.
Oh, also, we should have an fn every_ms(ms: u32) -> impl Stream<Output = ()>
, which would be a wrapper for setInterval
I've been thinking about this again. How about using the Timeout
object for all of these things, by providing some extra constructors: as well as new
there would be new_microtask
, new_task
(same as new(0, ...)
, and new_animation_frame
. They would then dispatch to the appropriate js functions.
How about using the
Timeout
object for all of these things ... there would benew_microtask
queueMicrotask()
does not return anything similar to timeoutID
and cannot be cancelled, so unless you're not planning on using queueMicrotask()
, the functionality will be pretty different. I'm not sure if it makes sense to add microtask code to Timeout
.