Open alextekartik opened 2 months ago
It doesn't have to be different across platforms, but it's allowed to.
Most likely all the different execution interleavings are within the specified behavior.
The async
/await
specification is generally specified as "at a later time" or "in a later microtask", without specifying precisely how microtasks are ordered or scheduled.
The async
/await
implementations differ between platforms, and may do different optimizations.
For example, an await asyncFunction()
call can validly be implemented by inlining asyncFunction
, which will definitely avoid introducing an extra await
-delay.
Even expecting the same platform to have exactly the same timing between releases, is to expect nothing to be optimized.
Generally, there is no guarantee about the order that completed futures call their callbacks in. It's mostly predictable only because nobody actively tried to make it unpredictable. To be blunt (and my opinion, not official policy): Code that breaks if timing of unrelated asynchronous operations change is bad code. Unrelated just means neither awaits the other.
If you manually call scheduleMicrotask
or Timer
, then you can somewhat predict the order of those callbacks, but futures are complicated objects with different optimizations depending on how they're used, and you should not expect to predict anything more complicated than calling complete
on a Completer
for a Future
with a single listener.
That said, traditionally an await 0;
has inserted a delay that allows other already scheduled microtasks to run. It is expected to introduce effectively await Future.microtask(()=>0)
which schedules a microtask at the end of the queue, because the specification said it was equivalent to Future.value(0)
, and that was how Future.value
worked (not how it is specified, just how it is implemented).
The await 0
shouldn't even have specified in terms of a specific constructor, and we should fix that too.
The order that other platforms complete in is a consequence of the internal behavior of a function named _propagateToListeners
which calls the listeners of a completed future, completes the futures returned by those callbacks and the continues calling those futures' callbacks in a loop. It propagates a future result as far as possible in a single step, effectively completing later futures synchronously.
It's interesting that dart2wasm behaves differently, because it might mean that it's not as efficient as possible. That's worth looking into.
But it's not wrong.
It's interesting that dart2wasm behaves differently, because it might mean that it's not as efficient as possible. That's worth looking into. But it's not wrong.
So I'll label this as performance improvement.
I'm fine with that. If(/when) someone comes along and says that it makes dart2wasm unusable for their code that things are not timed exactly the same way, then we can consider whether to appease them, or if they'll have to fix their code. (If those people are web developers inside Google, and we want to switch them from dart2js to dart2wasm, we may have to fix their code.)
Using dart 3.4.2 on Ubuntu 22.04
When using async/await, I noticed some difference in timing when methods are executed.
Consider the following unit test
When using
it succeeds and properly displays:
However when using
it fails and displays
I noticed this while doing some indexeddb testing using the new js_interop where timing matters a lot during transaction.
Is the timing difference expected or should it be consistent across platforms?