Flutter is based on dart, which is a single-threaded model. Future is also based on a single-threaded asynchronous mechanism, that is, asynchronous based on event loops. This is different from the asynchronous implementation of multithreading, which is more similar to the Handler mechanism in Android. The idea is to send a message to the center of the event loop, wait for a schedule, and execute code at a later point, but that code will still be executed in the current thread, so if you use the Future to execute a time-consuming task, it might not block the current UI process, but some subsequent UI operations will still be affected. Executing code asynchronously with a Future requires four steps:
- Create a task
- Send a task
- Perform a task
- Execute function code
The final task received is a callback, see also:
factory Future(FutureOr<T> computation()) {
_Future<T> result = new _Future<T>();
Timer.run(() {
try {
result._complete(computation());
} catch(e, s) { _completeWithErrorCallback(result, e, s); }});return result;
}
Copy the code
The _complete function is used to handle computation results and is divided into three types of judgment:
void _complete(FutureOr<T> value) {
assert(! _isComplete);if (value is Future<T>) {
if (value is _Future<T>) {
_chainCoreFuture(value, this);
} else {
_chainForeignFuture(value, this); }}else {
_FutureListener listeners = _removeListeners();
_setValue(value);
_propagateToListeners(this, listeners); }}Copy the code
There are two different types of processing logic when the result is still a Future, and when the result is not a Future object. The latter, which is the logic in the else statement, as you can see, is to pass the return value to the listener, The _propagateToListeners calls are then distributed to listeners, but the former, the logic in the if statement, tells us that when the last Future returned a Future, The Future cannot be directly treated as the value of the listener and then notified to the listener. This relates to one of the rules of Future usage: The listener cannot listen for a Future, so we have code like this:
test4() {
print("start");
Future(() {
print("return future");
return Future(() {
print("return test4");
return "test4";
});
}).then((FutureOr<String> s) {
if (s is Future<String>) {
s.then((String ss) {
print(ss + "_");
});
} else {
print(s); }});print("end");
}
Copy the code
This code ends up printing test4 instead of test4_, and this happens because of the different handling of Future and non-future objects in the _complete function. For non-future objects, the return value is passed to result via _setValue, which is then distributed to its listener. This is a fairly routine procedure. There are also two different ways of handling Future objects. _chainCoreFuture is called when the return value is a _Future object, and _chainForeignFuture is called otherwise. However, the default implementation of Flutter has some more “local” handling of _Future. For other Future implementations, the same function can only be implemented using the functions contained in the Future. A Future cannot be returned as a listener.
static void _chainForeignFuture(Future source, _Future target) {
assert(! target._isComplete);assert(source is! _Future);
// Mark the target as chained (and as such half-completed).
target._setPendingComplete();
try {
source.then((value) {
assert(target._isPendingComplete);
// The "value" may be another future if the foreign future
// implementation is mis-behaving,
// so use _complete instead of _completeWithValue.
target._clearPendingComplete(); // Clear this first, it's set again.
target._complete(value);
},
// TODO(floitsch): eventually we would like to make this non-optional
// and dependent on the listeners of the target future. If none of
// the target future's listeners want to have the stack trace we don't
// need a trace.
onError: (error, [StackTrace stackTrace]) {
assert(target._isPendingComplete);
target._completeError(error, stackTrace);
});
} catch (e, s) {
// This only happens if the `then` call threw synchronously when given
// valid arguments.
// That requires a non-conforming implementation of the Future interface,
// which should, hopefully, never happen.scheduleMicrotask(() { target._completeError(e, s); }); }}Copy the code
Source is the return value of the previous Future, target is the Future corresponding to the listener, and instead of setting source to the value of target, it continues to call the source’s then function, We get the return value from source and then we call target’s _complete function to process the return value, and then we get into a loop, and we call it as many times as the source returns nested futures, and anyway, until the source returns a regular value, That’s what _chainForeignFuture does. It just uses the Future’s then function, and it looks easy to understand, but _chainCoreFuture doesn’t look so nice.
static void _chainCoreFuture(_Future source, _Future target) {
assert(target._mayAddListener); // Not completed, not already chained.
while (source._isChained) {
source = source._chainSource;
}
if (source._isComplete) {
_FutureListener listeners = target._removeListeners();
target._cloneResult(source);
_propagateToListeners(target, listeners);
} else{ _FutureListener listeners = target._resultOrListeners; target._setChained(source); source._prependListeners(listeners); }}Copy the code
Identify whether the source is in the Future chain. If so, identify the Future at the end of the chain as the source. If the source has been completed, copy the return value of the source and distribute it to the listeners. If not, join the Future chain and transplant your listeners to the Source. So what is the Future chain? What’s the source at the end of the chain? Why can Target listeners be delivered directly to source? Welcome to… Let’s start with some sample code:
test5() {
Future<String> f = Future.delayed(Duration(milliseconds: 100), () {
print("return test5");
return "test5";
});
Future(() {
print("return f");
return f;
}).then((String s) => print(s));
}
test6() {
Future<String> f = Future.delayed(Duration(milliseconds: 100), () {
print("return test6");
return "test6";
});
Future<String> ff = Future(() {
print("return f");
return f;
});
Future<String> fff = Future(() {
print("return ff");
return ff;
});
Future(() {
print("return fff");
return fff;
}).then((String s) => print(s));
}
Copy the code
Test5 defines a Future that returns f with a delay of 100 ms, and a listener that takes a String. When the second Future returns f, f has not yet been executed, so this is the code segment that executes:
_FutureListener listeners = target._resultOrListeners;
target._setChained(source);
source._prependListeners(listeners);
Copy the code
The Future joins the Future chain and hands over listner to F, so the actual logic should look like this:
test5() {
Future<String> f = Future.delayed(Duration(milliseconds: 100), () {
print("return test5");
return "test5";
});
f.then((String s) => print(s));
}
Copy the code
So a double-nested Future becomes an unnested Future, and when f is done, listner is told to print S. Ff, FFF, and the last Future all return futures. so when ff is done, it returns an unfinished F, so it adds to the Future chain, ff -> f. Then FFF returns ff, and ff is already in the Future chain, so FFF will join the Future chain, FFF -> FF -> F, and Future -> FFF -> FF -> F, And the Future listener is attached to f, so it can also be simplified as:
test6() {
Future<String> f = Future.delayed(Duration(milliseconds: 100), () {
print("return test6");
return "test6";
});
f.then((String s) => print(s));
}
Copy the code
Finally, when f is finished, the _complete function executes the following code:
_FutureListener listeners = _removeListeners();
_setValue(value);
_propagateToListeners(this, listeners);
Copy the code
The listeners of ff, FFF, and Future are the listeners of ff, FFF, and Future, but they are now grafted onto F seamlessly, and fundamentally, Their listener is actually listening for the return value of f. A Future chain is a list of futures, but only one of them returns real data, the one at the end of the chain. The listeners of the preceding Future are directly or indirectly dependent on the result of the return of the tail Future, so they are also directly grafted onto the tail Future. That’s the source in _chainCoreFuture, and those futuresthat do not return real data are not called _propagateToListeners. _propagateToListeners is responsible for passing the return value to the listeners:
static void _propagateToListeners(_Future source, _FutureListener listeners) {
while (true) {
assert(source._isComplete);
bool hasError = source._hasError;
if (listeners == null) {
if (hasError) {
AsyncError asyncError = source._error;
source._zone
.handleUncaughtError(asyncError.error, asyncError.stackTrace);
}
return;
}
// Usually futures only have one listener. If they have several, we
// call handle them separately in recursive calls, continuing
// here only when there is only one listener left.
while(listeners._nextListener ! =null) {
_FutureListener listener = listeners;
listeners = listener._nextListener;
listener._nextListener = null;
_propagateToListeners(source, listener);
}
_FutureListener listener = listeners;
final sourceResult = source._resultOrListeners;
// Do the actual propagation.
// Set initial state of listenerHasError and listenerValueOrError. These
// variables are updated with the outcome of potential callbacks.
// Non-error results, including futures, are stored in
// listenerValueOrError and listenerHasError is set to false. Errors
// are stored in listenerValueOrError as an [AsyncError] and
// listenerHasError is set to true.
bool listenerHasError = hasError;
var listenerValueOrError = sourceResult;
// Only if we either have an error or callbacks, go into this, somewhat
// expensive, branch. Here we'll enter/leave the zone. Many futures
// don't have callbacks, so this is a significant optimization.
if (hasError || listener.handlesValue || listener.handlesComplete) {
Zone zone = listener._zone;
if(hasError && ! source._zone.inSameErrorZone(zone)) {// Don’t cross zone boundaries with errors.
AsyncError asyncError = source._error;
source._zone
.handleUncaughtError(asyncError.error, asyncError.stackTrace);
return;
}
Zone oldZone;
if(! identical(Zone.current, zone)) {// Change zone if it's not current.
oldZone = Zone._enter(zone);
}
// These callbacks are abstracted to isolate the try/catch blocks
// from the rest of the code to work around a V8 glass jaw.
void handleWhenCompleteCallback() {
// The whenComplete-handler is not combined with normal value/error
// handling. This means at most one handleX method is called per
// listener.
assert(! listener.handlesValue);assert(! listener.handlesError);var completeResult;
try {
completeResult = listener.handleWhenComplete();
} catch (e, s) {
if (hasError && identical(source._error.error, e)) {
listenerValueOrError = source._error;
} else {
listenerValueOrError = new AsyncError(e, s);
}
listenerHasError = true;
return;
}
if (completeResult is Future) {
if (completeResult is _Future && completeResult._isComplete) {
if (completeResult._hasError) {
listenerValueOrError = completeResult._error;
listenerHasError = true;
}
// Otherwise use the existing result of source.
return;
}
// We have to wait for the completeResult future to complete
// before knowing if it’s an error or we should use the result
// of source.
var originalSource = source;
listenerValueOrError = completeResult.then((_) => originalSource);
listenerHasError = false; }}void handleValueCallback() {
try {
listenerValueOrError = listener.handleValue(sourceResult);
} catch (e, s) {
listenerValueOrError = new AsyncError(e, s);
listenerHasError = true; }}void handleError() {
try {
AsyncError asyncError = source._error;
if (listener.matchesErrorTest(asyncError) &&
listener.hasErrorCallback) {
listenerValueOrError = listener.handleError(asyncError);
listenerHasError = false; }}catch (e, s) {
if (identical(source._error.error, e)) {
listenerValueOrError = source._error;
} else {
listenerValueOrError = new AsyncError(e, s);
}
listenerHasError = true; }}if (listener.handlesComplete) {
handleWhenCompleteCallback();
} else if(! hasError) {if(listener.handlesValue) { handleValueCallback(); }}else {
if(listener.handlesError) { handleError(); }}// If we changed zone, oldZone will not be null.
if(oldZone ! =null) Zone._leave(oldZone);
// If the listener’s value is a future we need to chain it. Note that
// this can only happen if there is a callback.
if (listenerValueOrError is Future) {
Future chainSource = listenerValueOrError;
// Shortcut if the chain-source is already completed. Just continue
// the loop.
_Future result = listener.result;
if (chainSource is _Future) {
if (chainSource._isComplete) {
listeners = result._removeListeners();
result._cloneResult(chainSource);
source = chainSource;
continue;
} else{ _chainCoreFuture(chainSource, result); }}else {
_chainForeignFuture(chainSource, result);
}
return;
}
}
_Future result = listener.result;
listeners = result._removeListeners();
if(! listenerHasError) { result._setValue(listenerValueOrError); }else {
AsyncError asyncError = listenerValueOrError;
result._setErrorObject(asyncError);
}
// Prepare for next round.source = result; }}Copy the code
This function is relatively heavy, and its functions are more complete, but the structure is still relatively clear, converted to:
static void _propagateToListeners(_Future source, _FutureListener listeners) {
while (true) {
// Distribute the result of the source to each listener on the Listeners using recursion
// If the listener has one of the three capabilities, execute one of them according to the situation
if (hasError || listener.handlesValue || listener.handlesComplete) {
/ / declare handleWhenCompleteCallback function
// Declare handleValueCallback
// Declare handleError
// Select one of the above functions to execute according to the listener capability and source execution result
// If the listener returns a Future, call _chainCoreFuture or _chainForeignFuture
}
// If a normal value is returned, value is set to result, and the result of result is then distributed to result listeners, so reassign values to source and Listeners, while loop}}Copy the code
That’s the vein. If the listener process results in a Future, see the following two examples:
test7() {
print("start");
Future<String> f = Future.delayed(Duration(milliseconds: 100), () {
print("return test7");
return "test7";
});
Future<String> ff = Future(() {
print("return f");
return f;
});
Future(() {
print("return ff");
return ff;
}).then((String s) {
return Future.delayed(Duration(milliseconds: 100), () {
print("return s");
return s;
});
}).then((String s) => print(s));
print("end");
}
test8() {
print("start");
Future.delayed(Duration(milliseconds: 100), () {
print("return test8");
return "test8";
}).then((String s) {
print("return s1");
return s;
}).then((String s) {
print("return s2");
return s;
}).then((String s) {
print("return s3");
return s;
}).then((String s) {
print(s);
});
print("end");
}
Copy the code
In test7, the Future returns ff. From the _complete function, test7 can do the following conversion:
test7() {
print("start");
Future<String> f = Future.delayed(Duration(milliseconds: 100), () {
print("return test7");
return "test7";
});
f.then((String s) {
return Future.delayed(Duration(milliseconds: 100), () {
print("return s");
return s;
});
}).then((String s) => print(s));
print("end");
}
Copy the code
Then, when f finishes executing, a result is passed to the Listener via _propagateToListeners, and the Listener’s callback is executed, but the result is still a Future. This code is executed:
if (listenerValueOrError is Future) {
Future chainSource = listenerValueOrError;
// Shortcut if the chain-source is already completed. Just continue
// the loop.
_Future result = listener.result;
if (chainSource is _Future) {
if (chainSource._isComplete) {
listeners = result._removeListeners();
result._cloneResult(chainSource);
source = chainSource;
continue;
} else{ _chainCoreFuture(chainSource, result); }}else {
_chainForeignFuture(chainSource, result);
}
return;
}
Copy the code
So, test7 will look like this again:
test7() {
print("start");
Future<String> f = Future.delayed(Duration(milliseconds: 100), () {
print("return test7");
return "test7";
});
f.then((String s) {
Future f = Future.delayed(Duration(milliseconds: 100), () {
print("return s");
return s;
});
f.then((String s) => print(s));
return f;
});
print("end");
}
Copy the code
At this point, when the Future in f has also been executed, the last listener is emitted and s is printed. For test8, multiple THEN concatenates and returns a regular value, which corresponds to this code:
_Future result = listener.result;
listeners = result._removeListeners();
if(! listenerHasError) { result._setValue(listenerValueOrError); }else {
AsyncError asyncError = listenerValueOrError;
result._setErrorObject(asyncError);
}
// Prepare for next round.
source = result;
Copy the code
The listeners are Future listeners, and the listeners are result listeners, so each time a while loop is executed, it signals the end of the THEN function. Until all listeners are processed. Then function:
Future<R> then<R>(FutureOr<R> f(T value), {Function onError}) {
Zone currentZone = Zone.current;
if(! identical(currentZone, _rootZone)) { f = currentZone.registerUnaryCallback<FutureOr<R>, T>(f);if(onError ! =null) {
// In checked mode, this checks that onError is assignable to one of:
// dynamic Function(Object)
// dynamic Function(Object, StackTrace)
onError = _registerErrorHandler(onError, currentZone);
}
}
_Future<R> result = new _Future<R>();
_addListener(new _FutureListener<T, R>.then(result, f, onError));
return result;
}
Copy the code
The _addListener function is used to add a Future listener, and the Future listener is also created in this function. Result, callback, and onError will be constructed as instances of _FutureListener, and _addListener is responsible for adding listeners to the Future.
void _addListener(_FutureListener listener) {
assert(listener._nextListener == null);
if (_mayAddListener) {
listener._nextListener = _resultOrListeners;
_resultOrListeners = listener;
} else {
if (_isChained) {
// Delegate listeners to chained source future.
// If the source is complete, instead copy its values and
// drop the chaining.
_Future source = _chainSource;
if(! source._isComplete) { source._addListener(listener);return;
}
_cloneResult(source);
}
assert(_isComplete);
// Handle late listeners asynchronously.
_zone.scheduleMicrotask(() {
_propagateToListeners(this, listener); }); }}Copy the code
Listeners are often added to Future listeners, either directly to the source when the Future is already part of the Future chain, or when the Future has been executed, The _propagateToListeners handle is called directly. How do you define parameters such as _isChained, _isComplete, and _mayAddListener? What are the _chainSource and _resultOrListeners? Collectively, these are two variables, _state and _resultOrListeners, such as:
bool get _mayComplete => _state == _stateIncomplete;
bool get _isPendingComplete => _state == _statePendingComplete;
bool get _mayAddListener => _state <= _statePendingComplete;
bool get _isChained => _state == _stateChained;
bool get _isComplete => _state >= _stateValue;
bool get _hasError => _state == _stateError;
Copy the code
Such as:
AsyncError get _error {
assert(_hasError);
return _resultOrListeners;
}
_Future get _chainSource {
assert(_isChained);
return _resultOrListeners;
}
_FutureListener _removeListeners() {
// Reverse listeners before returning them, so the resulting list is in
// subscription order.
assert(! _isComplete); _FutureListener current = _resultOrListeners; _resultOrListeners =null;
return _reverseListeners(current);
}
void _setChained(_Future source) {
assert(_mayAddListener);
_state = _stateChained;
_resultOrListeners = source;
}
void _setValue(T value) {
assert(! _isComplete);// But may have a completion pending.
_state = _stateValue;
_resultOrListeners = value;
}
void _setErrorObject(AsyncError error) {
assert(! _isComplete);// But may have a completion pending.
_state = _stateError;
_resultOrListeners = error;
}
void _cloneResult(_Future source) {
assert(! _isComplete);assert(source._isComplete);
_state = source._state;
_resultOrListeners = source._resultOrListeners;
}
Copy the code
Therefore, error, result, chainSource, and listeners are actually the same variable, but the _resultOrListeners have different meanings in different states. Under the _hasError state, __resultorlisteners on the record _resultOrListeners are listeners on _isComplete, and values for _resultOrListeners are usually assigned to _state listeners as well.