I'm working on a flutter application which requires using local storage. As the document directory path in Android will be given in Future<Directory>, I have to check whether the future finishes or not, each time I want to use the path.
The code may be similar to below
class DataStructure {
late Future<Directory> _dir = getApplicationDocumentsDirectory();
Future<void> read(String fileName) async {
Directory dir = await _dir;
// Do something using dir
}
}
This might be stupid, but I write C code in most of the time, so I want to reduce number of times pushing a function to the event queue for a better performance (I guess the compiler cuts async functions with await
into "functions" to be pushed to the event queue). So I wrote experiment code to check whether await on a finished future will cut the control flow or not. (I mean, in the newest version of Dart, async functions will execute until the first 'await' keyword arises. I'm wondering if this is still the case if the future has already been finished.)
Future<void> voidFuture(String name) async {
print('$name: voidFuture');
}
Future<void> printSingle(String name, int index) async {
print('$name: -- index = $index');
}
// This emit function will print one line each time
// it gets chance to be executed.
// Because the 'await' keyword will cut the control flow,
// it can only print once each time it's executed,
// and must wait for the next chance to print again.
// This feature makes it appropriate for testing
// when and where the control flow is cut,
// as each cut will lead to one line of output.
Future<void> emit(String name, int count) async {
for (int index = 0; index != count; index) {
await printSingle(name, index);
}
}
Future<void> task_0() async {
const String name = 'task_0';
Future<void> emitFinish = emit(name, 3);
await voidFuture(name);
print('$name: output after await');
await emitFinish;
}
Running task_0 in my environment (Dart SDK version: 2.18.5 (stable) on "windows_x64") gives output as below:
task_0: -- index = 0
task_0: voidFuture
task_0: -- index = 1
task_0: output after await
task_0: -- index = 2
Which is the same as what I expected. And the weird thing comes when I change the emit()
function:
Future<void> emit(String name, int count) async {
for (int index = 0; index != count; index) {
// Before:
// await printSingle(name, index);
// After:
await Future(() {
print('$name: -- index = $index');
});
}
}
Then the output becomes
task_0: voidFuture
task_0: output after await
task_0: -- index = 0
task_0: -- index = 1
task_0: -- index = 2
and it makes no sense for me that the third line, -- index = 0
comes after output after await
. It seems that a function is more privileged than a future from constructor?
And my main question is "Will 'await' waits for a finished future?", so I wrote code below:
Future<String> stringFuture() async {
return '.';
}
Future<void> task_3() async {
const String name = 'task_3';
Future<void> emitFinish = emit(name, 4);
Future<String> futureString = stringFuture();
print('$name: before await of futureString');
await futureString;
print('$name: 1st await of futureString over');
await 1;
print('$name: 1st await of constant over');
await 2;
print('$name: 2nd await of constant over');
await emitFinish;
}
With the first version of emit()
, the output is
task_3: -- index = 0
task_3: before await of futureString
task_3: -- index = 1
task_3: 1st await of futureString over
task_3: -- index = 2
task_3: 1st await of constant over
task_3: -- index = 3
task_3: 2nd await of constant over
Which means even await
for a constant integral will push the lines after it to the event queue.
(Of course, with the second version of emit()
all its output comes after the last print()
in task_3()
, and I don't know why)
I know there are manys work-arounds, one of them will be using a T? value
to be assigned after the first time the Future<T>
finishes, and check whether value == null
each time using it.
But the questions I want to ask are:
What does the keyword
await
do internally? Please come with details that are enough to explain phenomena above.Is there any way of overriding the default
await
behavior? E.g., by overriding a method?What's the preferred way of using a
Future
value for many times?(Unrelated to above) How to stop at the welcome page in Flutter to wait for these async functions, e.g.,
getApplicationDocumentsDirectory()
to finish before building all the widgets?
Most results I got from Google were introduction on async
and await
keywords for beginners, and I couldn't find much material explaining the behavior of await
in Dart API Reference Documentation.
Thank you for saving a heart broken by await
>_<
CodePudding user response:
await
is syntactic sugar for registering callbacks through the Future
API. For example:
Future<int> foo() async {
var x = await someIntFuture();
return otherStuff(x);
}
is basically transformed into:
Future<int> foo() {
return someIntFuture.then((x) {
return otherStuff(x);
});
}
await
registers a Future.then
callback and returns that new Future
. That it returns a Future
means that await
always yields (even in cases such as await null
). This is also why when you invoke an async
function, its body is executed synchronously until it reaches its first await
.
it makes no sense for me that the third line,
-- index = 0
comes afteroutput after await
. It seems that a function is more privileged than a future from constructor?
From the documentation for the Future
constructor:
Creates a future containing the result of calling
computation
asynchronously withTimer.run
.
The callback you supply to the Future
constructor is invoked asynchronously; it is scheduled. This is different from calling an async
function, which as stated before executes synchronously as much as possible first.
And my main question is "Will 'await' waits for a finished future?"
It doesn't matter if the Future
is already completed or not. await
always yields.
Is there any way of overriding the default
await
behavior? E.g., by overriding a method?
As mentioned, await
is syntactic sugar. What you could do is to create a class that implements the Future
API and handles .then
differently (which is what Flutter's SynchronousFuture
class does), but I wouldn't recommend it (for the same reasons why the SynchronousFuture
documentation discourages its use).
What's the preferred way of using a
Future
value for many times?
Depends on the situation. In general, try to await
the Future
once and store the result somewhere (such as in a local variable). Otherwise I'd just await
the Future
multiple times and not worry about it until there's evidence that it's performance-critical.
(Unrelated to above) How to stop at the welcome page in Flutter to wait for these async functions
Depends. For some things, you can simply make your main
function async
and await
whatever asynchronous initialization you want to do before calling runApp
. In other cases (particularly for long-running ones), you should use a FutureBuilder
.
(Also, in the future, separate questions should be asked separately.)