is it possible to create your own futures in Dart to return from your methods, or must you always return a built in future return from one of the dart async libraries methods?>
This answer is a summary of the many ways you can do it.
Your method could be anything but for the sake of these examples, let's say your method is the following:
int cubed(int a) {
return a * a * a;
}
Currently you can use your method like so:
int myCubedInt = cubed(3); // 27
However, you want your method to return a Future
like this:
Future myFutureCubedInt = cubed(3);
Or to be able to use it more practically like this:
int myCubedInt = await cubed(3);
The following solutions all show ways to do that.
The most basic solution is to use the generative constructor of Future
.
Future cubed(int a) {
return Future(() => a * a * a);
}
I changed the return type of the method to Future
and then passed in the work of the old function as an anonymous function to the Future
constructor.
Futures can complete with either a value or an error. Thus if you want to specify either of these options explicitly you can use the Future.value
or Future.error
named constructors.
Future cubed(int a) {
if (a < 0) {
return Future.error(ArgumentError("'a' must be positive."));
}
return Future.value(a * a * a);
}
Not allowing a negative value for a
is a contrived example to show the use of the Future.error
constructor. If there is nothing that would produce an error then you can simply use the Future.value
constructor like so:
Future cubed(int a) {
return Future.value(a * a * a);
}
An async
method automatically returns a Future
so you can just mark the method async
and change the return type like so:
Future cubed(int a) async {
return a * a * a;
}
Normally you use async
in combination with await
, but there is nothing that says you must do that. Dart automatically converts the return value to a Future
.
In the case that you are using another API that returns a Future
within the body of your function, you can use await
like so:
Future cubed(int a) async {
return await cubedOnRemoteServer(a);
}
Or this is the same thing using the Future.then
syntax:
Future cubed(int a) async {
return cubedOnRemoteServer(a).then((result) => result);
}
Using a Completer
is the most low level solution. You only need to do this if you have some complex logic that the solutions above won't cover.
import 'dart:async';
Future cubed(int a) async {
final completer = Completer();
if (a < 0) {
completer.completeError(ArgumentError("'a' must be positive."));
} else {
completer.complete(a * a * a);
}
return completer.future;
}
This example is similar to the named constructor solution above. It handles errors in addition completing the future in the normal way.
There is nothing about using a future that guarantees you won't block the UI (that is, the main isolate). Returning a future from your function simply tells Dart to schedule the task at the end of the event queue. If that task is intensive, it will still block the UI when the event loop schedules it to run.
If you have an intensive task that you want to run on another isolate, then you must spawn a new isolate to run it on. When the task completes on the other isolate, it will return a message as a future, which you can pass on as the result of your function.
Many of the standard Dart IO classes (like File
or HttpClient
) have methods that delegate the work to the system and thus don't do their intensive work on your UI thread. So the futures that these methods return are safe from blocking your UI.