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!";});
}
Related
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();
}
...
}
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.
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;
}
I am writing a Future method in Dart (flutter). It simply runs a query on Firebase and returns the result. But even before writing my business logic, I am getting a warning message says:
[dart] This function has a return type of 'Future', but
doesn't end with a return statement. [missing_return]
Below is my Future function:
Future<String> getLikeCount(documentID) async {
Firestore.instance.collection('favorites').where(documentID).getDocuments().then((data){
return 'test';
});
}
I get the basic idea of why the error is happening, I assume that because there is a 'then' inside, till it happens the function returns nothing. How to overcome this issue?
Use await instead of then because your method is async
final snapshot = await Firestore.instance.collection('favorites').where(documentID).getDocuments();
return "test";
Change this :
_getLikes() async
To this :
Future<String> _getLikes() async
Because you expect a String Future.
try this without async
Future<String> getLikeCount(documentID) {
return Firestore.instance.collection('favorites').where(documentID).getDocuments().then((data){
return 'test';
});
}
I make a function call to my database, which updates a local object after getting data and takes a few moments.
Because of the Async task, the program moves to the next line of code. unfortunately I need the local object that gets updated with the async call for the next line of code.
how can I wait for my async task to finish before the next piece of code is executed? thank you
edit: adding code to explain
updateUser() {
return FutureBuilder(
future: updateUserData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return Text("hello not");
} else {
return Text('Hello!');
}
},
);}
#override
Widget build(BuildContext context) {
switch (_authStatus) {
case AuthStatus.notSignedIn:
return new LoginPage(
auth: auth,
CurrentUser: CurrentUser,
onSignedIn: _signedIn,
);
case AuthStatus.signedIn:
{
updateUser(); //THIS TAKES A COUPLE SECONDS TO FINISH BUT I NEED TO SEND IT TO THE NEXT PAGE
return new HomePage(
auth: auth,
CurrentUser: CurrentUser,
onSignedOut: _signedOut,
);
}
}
}
}
You can use await keyword in async function.
eg:
void someFunc() async {
await someFutureFunction();
// Your block of code
}
Here your block of code wont run until someFutureFunction returns something.
You can also use with custom async function like below example:
(() async {
await restApis.getSearchedProducts(widget.sub_cats_id,widget.keyword).then((val) => setState(()
{
setState(() {
data = val["data"];
});
}));
})();
This might help, The below sample code has two functions,
Below function is used to load the assets and return the JSON content.
Future<String> _loadCountriesAsset() async {
return await rootBundle.loadString('assets/country_codes.json');
}
the other function will use the JSON content and convert the format to model and return to the class.
Future<List<CountryCode>> loadCountryCodes() async {
String jsonString = await _loadCountriesAsset();
final jsonResponse = json.decode(jsonString);
// print(jsonResponse);
CountriesCodeList countriesCodeList =
new CountriesCodeList.fromJson(jsonResponse);
// print("length " + countriesCodeList.codes.length.toString());
return countriesCodeList.codes;
}
Usage in the class, defined a method to call the loadCountryCodes() function from services.dart file.
Future getCountryCodes() async {
var countryCodesList = await loadCountryCodes();
print("**** length " + countryCodesList.length.toString());
// Perform the operations, or update to the UI or server. It should be same for API call as well.
}
Hope this helps.