// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. part of "dart:async"; typedef void _AsyncCallback(); class _AsyncCallbackEntry { final _AsyncCallback callback; _AsyncCallbackEntry? next; _AsyncCallbackEntry(this.callback); } /// Head of single linked list of pending callbacks. _AsyncCallbackEntry? _nextCallback; /// Tail of single linked list of pending callbacks. _AsyncCallbackEntry? _lastCallback; /// Tail of priority callbacks added by the currently executing callback. /// /// Priority callbacks are put at the beginning of the /// callback queue, so that if one callback schedules more than one /// priority callback, they are still enqueued in scheduling order. _AsyncCallbackEntry? _lastPriorityCallback; /// Whether we are currently inside the callback loop. /// /// If we are inside the loop, we never need to schedule the loop, /// even if adding a first element. bool _isInCallbackLoop = false; void _microtaskLoop() { for (var entry = _nextCallback; entry != null; entry = _nextCallback) { _lastPriorityCallback = null; var next = entry.next; _nextCallback = next; if (next == null) _lastCallback = null; _microtaskEntryCallback(entry)(); } } void _startMicrotaskLoop() { _isInCallbackLoop = true; try { // Moved to separate function because try-finally prevents // good optimization. _microtaskLoop(); } finally { _lastPriorityCallback = null; _isInCallbackLoop = false; if (_nextCallback != null) { _AsyncRun._scheduleImmediate(_startMicrotaskLoop); } } } /// Schedules a callback to be called as a microtask. /// /// The microtask is called after all other currently scheduled /// microtasks, but as part of the current system event. void _scheduleAsyncCallback(_AsyncCallback callback) { _AsyncCallbackEntry newEntry = _AsyncCallbackEntry(callback); _beforeScheduleMicrotaskCallback(); _AsyncCallbackEntry? lastCallback = _lastCallback; if (lastCallback == null) { _nextCallback = _lastCallback = newEntry; if (!_isInCallbackLoop) { _AsyncRun._scheduleImmediate(_startMicrotaskLoop); } } else { lastCallback.next = newEntry; _lastCallback = newEntry; } } /// Schedules a callback to be called before all other currently scheduled ones. /// /// This callback takes priority over existing scheduled callbacks. /// It is only used internally to give higher priority to error reporting. /// /// Is always run in the root zone. void _schedulePriorityAsyncCallback(_AsyncCallback callback) { if (_nextCallback == null) { _scheduleAsyncCallback(callback); _lastPriorityCallback = _lastCallback; return; } _AsyncCallbackEntry entry = _AsyncCallbackEntry(callback); _beforeSchedulePriorityCallback(); _AsyncCallbackEntry? lastPriorityCallback = _lastPriorityCallback; if (lastPriorityCallback == null) { entry.next = _nextCallback; _nextCallback = _lastPriorityCallback = entry; } else { var next = lastPriorityCallback.next; entry.next = next; lastPriorityCallback.next = entry; _lastPriorityCallback = entry; if (next == null) { _lastCallback = entry; } } } // Overridden by VM. @pragma("dart2js:prefer-inline") @pragma("dart2wasm:prefer-inline") void _beforeSchedulePriorityCallback() {} @pragma("dart2js:prefer-inline") @pragma("dart2wasm:prefer-inline") void _beforeScheduleMicrotaskCallback() {} @pragma("dart2js:prefer-inline") @pragma("dart2wasm:prefer-inline") void Function() _microtaskEntryCallback(_AsyncCallbackEntry entry) => entry.callback; /// Runs a function asynchronously. /// /// Callbacks registered through this function are always executed in order and /// are guaranteed to run before other asynchronous events (like [Timer] events, /// or DOM events). /// /// **Warning:** it is possible to starve the DOM by registering asynchronous /// callbacks through this method. For example the following program runs /// the callbacks without ever giving the Timer callback a chance to execute: /// ```dart /// main() { /// Timer.run(() { print("executed"); }); // Will never be executed. /// foo() { /// scheduleMicrotask(foo); // Schedules [foo] in front of other events. /// } /// foo(); /// } /// ``` /// ## Other resources /// /// * [The Event Loop and Dart](https://dart.dev/articles/event-loop/): /// Learn how Dart handles the event queue and microtask queue, so you can write /// better asynchronous code with fewer surprises. @pragma('vm:entry-point', 'call') void scheduleMicrotask(void Function() callback) { _Zone currentZone = Zone._current; if (identical(_rootZone, currentZone)) { // No need to bind the callback. We know that the root's scheduleMicrotask // will be invoked in the root zone. _rootScheduleMicrotask(null, null, _rootZone, callback); return; } _ZoneFunction implementation = currentZone._scheduleMicrotask; if (identical(_rootZone, implementation.zone) && _rootZone.inSameErrorZone(currentZone)) { _rootScheduleMicrotask( null, null, currentZone, currentZone.registerCallback(callback), ); return; } Zone.current.scheduleMicrotask(Zone.current.bindCallbackGuarded(callback)); } class _AsyncRun { /// Schedule the given callback before any other event in the event-loop. external static void _scheduleImmediate(void Function() callback); }