Future<void>, async, await, then, catchError in Flutter/Dart - asynchronous

What is the difference between:
Future<void>, async, await, then, catchError
Future<void> copyToClipboard(BuildContext context, String text) async {
await Clipboard.setData(ClipboardData(text: text))
.then((_) => showSnackBar(context, 'Copied to clipboard'))
.catchError((Object error) => showSnackBar(context, 'Error $error'));
}
void, async, await, then, catchError
void copyToClipboard(BuildContext context, String text) async {
await Clipboard.setData(ClipboardData(text: text))
.then((_) => showSnackBar(context, 'Copied to clipboard'))
.catchError((Object error) => showSnackBar(context, 'Error $error'));
}
void, then, catchError
void copyToClipboard(BuildContext context, String text) {
Clipboard.setData(ClipboardData(text: text))
.then((_) => showSnackBar(context, 'Copied to clipboard'))
.catchError((Object error) => showSnackBar(context, 'Error $error'));
}
All methods work. If I'm using then and catchError, do I still need to wrap the code in an async function?
Which is the recommended way?

First of, the concept of async/await and then is basically the same. Many people say that async/await is the more elegant way to deal with Promises (because it looks more structured). What both ideas do is: "Do something and once it is done, do something else".
Regarding Future<void> copyToClipboard(BuildContext context, String text) async {...}:
Here you are returning a Promise from your function. Meaning, that you can call the function from somewhere else with
await copyToClipboard(context,"Text");
print("Done");
You can, however also return the Promise itself (and then handle it wherever you call that function):
Future<void> copyToClipboard(BuildContext context, String text) async {
return Clipboard.setData(ClipboardData(text: text));
}
void somewhereElse() async{
await copyToClipboard(context,"Text"); // (1)
print("Copied"); //Happens after (1)
}
So if you are using then, you will put the following instructions into the corresponding function called by it (as seen in your 3rd snippet). Solving the 2nd snippet with async would looke like this:
void copyToClipboard(BuildContext context, String text) async {
await Clipboard.setData(ClipboardData(text: text));// (1)
showSnackBar(context, 'Copied to clipboard')); // (2) This will only be called when (1) has completed
}
If this instruction throws an error, you can wrap it into a try/catch block:
void copyToClipboard(BuildContext context, String text) async {
try{
await Clipboard.setData(ClipboardData(text: text));// (1)
showSnackBar(context, 'Copied to clipboard')); // (2) This will only be called when (1) has completed
}catch (error) {
print(error.message);
}
}
If you prefer then/wait or async/await is up to you. I would recommend async/await. Keep in mind that when using async/await, you need to put the async keyword into the signature of the function, so functions calling this function are aware, that calling it may take a while and wait for it.

Related

Dart - Distinguish between sync and async void callbacks

Suppose we have the following method:
Future<String> foo(void callback()) async{
...
callback();
...
}
Then the callback is allowed to be either sync or async when defined. Is there a clean way to await it if it is async? Now if I await it here I get this warning from IDE:
'await' applied to 'void', which is not a 'Future'
You can't. The whole point of making an async function return void instead of Future<void> is so that the function is "fire-and-forget" and so that the caller can't wait for it to complete.
If you want to allow your foo function to take asynchronous functions as arguments, then its parameter should be a Future<void> Function() instead. That way callers can easily adapt a synchronous function with an asynchronous wrapper. (The reverse is not possible.)
Alternatively you could make the callback parameter return a FutureOr<void>:
Future<String> foo(FutureOr<void> Function callback) async {
...
await callback();
...
}
or if you don't want an unnecessary await if callback is synchronous:
Future<String> foo(FutureOr<void> Function callback) async {
...
if (callback is Future<void> Function) {
await callback();
} else {
callback();
}
...
}

Run async code in synchronous way in Dart

Having the following code:
Future<String> checkPrinter() async {
await new Future.delayed(const Duration(seconds: 3));
return Future.value("Ok");
}
String getPrinterStatus() {
checkPrinter().then((value) {
return 'The printer answered: $value';
}).catchError((_) {
return "Printer does not respond!";
});
}
void main() {
print(getPrinterStatus());
}
The output is "null" because the function getPrinterStatus() returns without waiting for checkPrinter to complete (correctly i have a warning telling me that getPrinterStatus does not return a string).
What should i do to make getPrinterStatus to wait for checkPrinter()?
Future<String> getPrinterStatus() async {
await checkPrinter().then((value) {
return 'The printer answered: $value';
}).catchError((_){
return "Printer does not respond!";});
}
There are no such way to make async code to run in a synchronous way in Dart. So you need to make it so async methods returns a Future so it is possible to await the answer.
The problem you have in your code is your are using the then method but does not take into account that this method returns a Future you should return. So e.g.:
String getPrinterStatus() {
checkPrinter().then((value) {
return 'The printer answered: $value';
}).catchError((_){
return "Printer does not respond!";});
}
The two return statements in this example are used for the method your are giving as argument for then() and are therefore not used to return from getPrinterStatus().
Instead, then() returns a Future<String> which will complete with the value.
So you need to carry the async through your program. Your code could make use of some language features you gets from marking a method async so I have tried to rewrite your code so it works as expected and make use of this features:
Future<String> checkPrinter() async {
await Future.delayed(const Duration(seconds: 3));
return "Ok";
}
// Just an example that the method could be written like this.
// Future.delayed takes a computation as argument.
Future<String> checkPrinterShorter() =>
Future.delayed(const Duration(seconds: 3), () => 'Ok');
Future<String> getPrinterStatus() async {
try {
return 'The printer answered: ${await checkPrinter()}';
} catch (_) {
return "Printer does not respond!";
}
}
Future<void> main() async {
print(await getPrinterStatus());
}
There are some changes I think you should notice:
If a method is marked as async the returned value will automatically be packed into a Future<Type> so you don't need to return Future.value("Ok") but can just return Ok.
If a method is marked as async you can use await instead of using then(). Also, you can use normal exception handling so I have rewritten getPrinterStatus to do that.
Your getPrinterStatus() is not async. main() function will never wait for it's execution.
Make it async. Refactor your main() for something like
void main() {
getPrinterStatus().then((value) {
print(value);
}).catchError((_){
return "Printer does not respond!";});
}

What's the difference between Future.error and throw in dart Future?

I'd like to understand when to use throw and return Future.error in async functions. Are there any underlying differences that we should take in consideration when choosing one or the other?
I did a small test:
Future testFuture() async {
return Future.error('error from future');
}
void testThrow() {
throw('error from throw');
}
Future testFutureThrow() async {
throw('error from future throw');
}
main() async {
print('will run test');
try{
await testFuture();
}
catch(e){
print(e);
}
try{
testThrow();
}
catch(e){
print(e);
}
try{
await testFutureThrow();
}
catch(e){
print(e);
}
testFuture().catchError((e) => print(e));
testFutureThrow().catchError((e) => print(e));
print('did run test');
}
They both seem to work in the same way. This is the output:
will run test
error from future
error from throw
error from future throw
did run test
error from future
error from future throw
What we can see from this test is that, from the point of view of the caller, when call the function using try/catch the code runs synchronous and if we Future.catchError it's asynchronous. But from the point of view of the function flow it appears to be the same.
NOTE: I had this initially as a response to a question. But then I realised that I was not answering but instead doing a question.
async and await are a language extension/syntactic sugar to simplify working with asynchronous code by restoring a synchronous fashion.
Both abstract away working with Futures directly, making this code analogous:
// On the callee's side
Future<String> calleeAsync() async {
return "from async callee";
}
Future<String> calleeSync() {
return Future.value("from sync callee");
}
// On the caller's side
Future<void> callerAsync() async {
print("got ${await calleeAsync()}");
print("got ${await calleeSync()}");
}
void callerSync() {
calleeAsync()
.then((v) => print("got3 $v"))
.then((_) => calleeSync())
.then((v) => print("got4 $v"));
}
(while in callerSync the second then()'s callback could be merged with the first's, and the third then() could also be attached directly to calleeSync() because of monad-like associativity)
In the same manner, these functions are equivalent:
// On the callee's side
Future<void> calleeAsync() async {
throw "error from async";
}
Future<void> calleeSync() {
return Future.error("error from sync");
}
// On the caller's side
Future<void> callerAsync() async {
try {
await calleeAsync();
} catch (e) {
print("caught $e");
}
try {
await calleeSync();
} catch (e) {
print("caught $e");
}
}
void callerSync() {
calleeAsync()
.catchError((e) => print("caught $e"))
.then((_) => calleeSync())
.catchError((e) => print("caught $e"));
}
In summary, there is no practical difference. return Future.error(…) simply doesn't make use of the extensions an async function provides, thus it's more like mixing two flavours of asynchronous code.
My experience is that if you want to use myFunc().then(processValue).catchError(handleError); you should return Future.error but not throw it.

How can I correctly yield from a stream that uses Futures/async/await?

I've encountered a weird issue where if I yield* from my provider in my flutter app, the rest of the code in the function doesn't complete.
I'm using the BLoC pattern, so my _mapEventToState function looks like this:
Stream<WizardState> _mapJoiningCongregationToState(
int identifier, int password) async* {
_subscription?.cancel();
_subscription= (_provider.doThings(
id: identifier, password: password))
.listen((progress) => {
dispatch(Event(
progressMessage: progress.progressText))
}, onError: (error){
print(error);
}, onDone: (){
print('done joiining');
});
}
Then in the provider/service... this is the first attempt.
final StreamController<Progress> _progressStream = StreamController<JoinCongregationProgress>();
#override
Stream<JoinCongregationProgress> doThings(
{int id, int password}) async* {
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake1..."));
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake5!!!..."));
yield* _progressStream.stream;
}
The yield statement returns, but only after both awaited functions have completed. This makes complete sense to me, obviously I wouldn't expect the code to complete out of order and somehow run the yield* before waiting for the 'await's to complete.
In order to "subscribe" to the progress of this service though, I need to yield the stream back up to the caller, to write updates on the UI etc. In my mind, this is as simple as moving the yield* to before the first await. Like this.
final StreamController<Progress> _progressStream = StreamController<JoinCongregationProgress>();
#override
Stream<JoinCongregationProgress> doThings(
{int id, int password}) async* {
yield* _progressStream.stream;
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake1..."));
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake5!!!..."));
}
But, then setting breakpoints on the later _progressStream.add calls show that these never get called. I'm stuck on this, any idea what it could be? I know it has something to do with how I have mixed Futures and Streams.
The yield* awaits the completion of the stream it returns.
In this case, you want to return a stream immediately, then asynchronously feed some data into that stream.
Is anything else adding events to the stream controller? If not, you should be able to just do:
#override
Stream<JoinCongregationProgress> doThings({int id, int password}) async* {
await Future.delayed(Duration(seconds:2));
yield JoinCongregationProgress(progressText: "kake1...");
await Future.delayed(Duration(seconds:2));
yield JoinCongregationProgress(progressText: "kake5!!!...");
}
No stream controller is needed.
If other functions also add to the stream controller, then you do need it. You then have to splut your stream creation into an async part which updates the stream controller, and a synchronous part which returns the stream. Maybe:
final StreamController<Progress> _progressStream = StreamController<JoinCongregationProgress>();
#override
Stream<JoinCongregationProgress> doThings({int id, int password}) {
() async {
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake1..."));
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake5!!!..."));
}(); // Spin off async background task to update stream controller.
return _progressStream.stream;
}

Flutter : Question about Futures and async function

I have several questions regarding the use of Futures in dart. Let's say I am working with a firestore and I have a function like this to update users's info :
void updateOldUser(User oldUser,String newInfo){
DocumentReference userToUpdateRef = userRef.document(oldUser.id);
Firestore.instance.runTransaction((Transaction transaction) async {
DocumentSnapshot userToUpdateSnapshot = await transaction.get(userToUpdateRef);
if(userToUpdateSnapshot.exists){
await transaction.update(
userToUpdateSnapshot.reference, userToUpdateSnapshot.data[newInfo] + 1
).catchError((e)=> print(e));
}
});
}
My question is : does it need to return a future since runTransaction is Future. It seems to work fine without it but to me it feels like it should return a "Future void" in order to be able to await updateOldUser when I use it. But when I turn it into a "Future void" and then end the function body with 'return;' I have an error saying 'expected a value after return'.
But what I really don't understand is that with another similar code :
Future<void> updateUserPhoto(User user,File userPhoto) async {
String photoUrl = await uploadImage(user.id,userPhoto);
DocumentReference userToUpdateRef = userRef.document(user.id);
Firestore.instance.runTransaction((Transaction transaction) async {
DocumentSnapshot userToUpdateSnapshot = await transaction.get(userToUpdateRef);
if(userToUpdateSnapshot.exists){
await transaction.update(
userToUpdateSnapshot.reference, {
'photoUrl' : photoUrl
}
).catchError((e)=> print(e));
}
});
return;
}
I don't get this error and It work fine also. Why ? Thanks in advance.
does it need to return a future
If you want the caller to be able to await the completion of the function, then the return type should be Future<...> (like Future<void> if there is no concrete return value, or Future<int> if the result is an integer value, ...)
For fire-and-forget async functions you can use void, but that is rather uncommon.
If the return type is Future<...> the caller can still decide not to wait for completion anyway.
But when I turn it into a "Future void" and then end the function body with 'return;'
If you use async, a Future is returned implicitly at the end of the method.
If you do not use async, you need to return a Future like
return doSomething.then((v) => doSomethingElse());

Resources