How can I wait for a method that doesn't return a Future? - asynchronous

I am building a screen in an app using an existing model class which I can't modify. The model has a method called refreshList() which fetches some data from an API, processes it, and finally it updates a List member in a model with the fetched data.
I want to know the state of the list - i.e. is it uninitialized, being fetched or already fetched - so that I can update the UI accordingly.
The problem is that refreshList() is not async, but uses an async method internally. Additionally, the internal method doesn't return a Future.
void refreshList(){
if( someCondition ){
_doRefreshList();
}
}
void _doRefreshList() async{
// ... do stuff
http.Response response = await http.get(url);
// ... do some more stuff
}
I would like to do something like
setState((){ _isLoading = true; });
await model.refreshList();
setState((){ _isLoading = false; });
but unfortunately I can't modify the methods in the model to return a Future.
Is there any way to achieve this without modifying refreshList() to be async and return a Future, and modifying _doRefreshList() to return a Future?

No, there is no way to wait for an asynchronous operation to complete unless it provides some way to get a callback when it's done.
The usual way to do that is to return a future. Having a callback function parameter is another approach, but it's rarely better than just returning a future.
In your example, refreshVerList has no way to report when it is done, and neither does _doRefreshList. You will have to add that somehow, and returning a future is the simplest and most consistent way to solve the problem for both functions.
(Technically, the _doRefreshList function does return a future, it's just typed as void. You can cast that future to Future, but I strongly discourage that kinds of shenanigans. If the function author decides to return something else, say null, at a later point, they are perfectly within their right to do since the return type is void.)

You can use Completer.
Completer<Null> onFetchCompleter;
Future<Null> _doRefreshList() async{
onFetchCompleter = Completer();
// ... do stuff
http.Response response = await http.get(url);
// ... do some more stuff
onFetchCompleter.complete();
return onFetchCompleter.future;
}
In the same way you can user a completer in your refreshVerList method.

Create a Future with the function reference as argument:
Future(model.refreshList);
Now you can use await since the above gives you a Future<void>:
setState(() { _isLoading = true; });
await Future(model.refreshList);
setState(() { _isLoading = false; });

Related

Dart - return data from a Stream Subscription block

I subscribe to a stream with the listen method so I can process the data as it comes in. If I found what I am looking for, I want to cancel the stream and return the data from the stream subscription block.
I have a working implementation but I am using a Completer to block the function from returning immediate and it feels messy, so I want to know if there's a better way to achieve this. Here is my code snippet:
Future<String> _extractInfo(String url) async {
var data = <int>[];
var client = http.Client();
var request = http.Request('GET', Uri.parse(url));
var response = await client.send(request);
var process = Completer();
var interestingData = '';
StreamSubscription watch;
watch = response.stream.listen((value) async {
data.clear(); //clear the previous stale data from the list so searching can be faster
data.addAll(value);
if (
hasInterestingData(data) //search the bytelist for the data I'm looking for
){
interestingData = extractInterestingData(data) //extract the data if it's been found. This is what I want my function to return;
await watch.cancel(); //cancel the stream subscription if the data has been found. ie: Cancel the download
process.complete('done'); //complete the future, so the function can return interestingData.
}
});
watch.onDone(() {
//complete the future if the data was not found...so the function will return an empty string
if (!process.isCompleted) {
process.complete('done');
}
});
await process.future; //blocks the sync ops below so the function doesn't return immediately
client.close();
return interestingData;
}
You should use await for for things like this when you are in an async function.
Future<String> _extractInfo(String url) async {
var client = http.Client();
var request = http.Request('GET', Uri.parse(url));
var response = await client.send(request);
await for (var data in response.stream) {
if (hasInterestingData(data)) {
return extractInterestingData(data); // Exiting loop cancels the subscription.
}
}
return "done";
}
However, while this approach is equivalent to your code, it's probably equally flawed.
Unless you are looking for a single byte, you risk the thing you are looking for being split between consecutive data events. You like do need to retain some part of the previous data array, or some state summarizing what you have already seen, but how much depends on what you are actually looking for.

Call async function in a sync way in Dart

I have this function, want to call it synchronously, because if I call it async then I should use FutureBuilder, which I don't prefer because it has extra flicker if user scrolls too fast:
Future<String> getRealHTML(int chapter) async {
var key = _Html.keys.toList()[chapter];
var val = _Html.values.toList()[chapter];
if (val.Content.startsWith("filename:")) {
EpubContentFileRef value = contentRefHtml[key];
return await value.readContentAsText();
}
return null;
}
readContentAsText() returns a Future, you cannot call it synchronously since it is an I/O request which means it will take time to finish the request, that is why it is called asychronously.

Dart Lang: Avoid marking all functions with async

Dart newbie here, I'm currently learning about asynchronous execution in Dart. I'm a bit irritated about how concurrency works in Dart, take the following scenario from their codelab:
void printOrderMessage () async {
try {
var order = await fetchUserOrder();
print('Awaiting user order...');
print(order);
} catch (err) {
print('Caught error: $err');
}
}
Future<String> fetchUserOrder() {
// Imagine that this function is more complex.
var str = Future.delayed(Duration(seconds: 4), () => throw 'Cannot locate user order');
return str;
}
Future<void> main() async {
await printOrderMessage();
}
In this case, the asynchronous operation is fetching the the user order from say a DB. Now because of Dart's await / async mechanism, every function that is related to the async operation is required to have a Future<> return type and must be tagged with async.
This feels clunky ... Imagine if some value deep in my function chain is being calculated asynchronously, would I really need to always return a future? Is there some other construct to synchronize code in Dart than await? Or have I misunderstood the concept?
If callers need to be able to wait for your asynchronous operation to finish, then your asynchronous function must return a Future that can be awaited. This is contagious; if callers of those callers need to be able to wait, then they too need to have Futures to wait upon.
If callers should not wait, then you can have a "fire-and-forget" function that does not need to return a Future:
Future<void> foo() {
// ...
}
// Does not need to return a Future. Consequently, callers cannot
// directly determine when `foo` completes.
void bar() {
foo();
}

Should I return result of AutoMapper using await Task.FromResult() in an async method?

All my repository methods are async for my CRUD operations using EF.Core async methods and use AutoMapper to map my EF entity model (Customer) to Domain Model (CustomerModel) and pass it back to my service layer.
public async Task<CustomerModel> GetCustomerByIdAsync(int id)
{
Customer customer = await _context.Customers.FindAsync(id);
var mappedResult = _mapper.Map<CustomerModel>(customer);
return await Task.FromResult(mappedResult);
}
Since "_mapper.Map" is not async but GetCustomerByIdAsync is, I return await Task.FromResult(mappedResult) instead of return mappedResult;
My caller method in service layer is also async. Have I got this right? I am also wondering about deadlock implications, although I am not calling any of my async methods from sync methods using .Result.
Thanks for your time.
Since "_mapper.Map" is not async but GetCustomerByIdAsync is, I return await Task.FromResult(mappedResult) instead of return mappedResult;
No, await Task.FromResult(x) doesn't ever make any sense. I've seen this code a lot, and I'm not sure where it comes from.
await Task.FromResult(x) is literally a less-efficient way of writing x. It's less efficient and less maintainable, so I have no idea where this idea came from.
Your code should look like:
public async Task<CustomerModel> GetCustomerByIdAsync(int id)
{
Customer customer = await _context.Customers.FindAsync(id);
var mappedResult = _mapper.Map<CustomerModel>(customer);
return mappedResult;
}

Should i return a value for a Future<void> Function?

In Dart, there are 2 options for functions that run asychronously, but whose return values are not supposed to be used.
Future<Null>
Future<void>
Since Dart 2.0, it is often recommended to use void instead of Null in most cases (due to the warning that void types shouldn't be used).
For these, a return type should not be required, but one could return all types. But what is the best practice here, should i still return something, should i end the function with a empty return (1), return null or something else (2) or should i just end the function (3)? Did i maybe miss any essential differences between the 3 options, supposing the return value doesnt get used (f.e. await exampleFunction() should in all cases wait till the function completes)? Is it essentially just code style?
1)
Future<void> exampleFunction() async{
...
return;
}
2)
Future<void> exampleFunction() async{
...
return null;
}
3)
Future<void> exampleFunction() async{
...
}
If an async method returns Future<void> the best practice is to avoid the empty return at its end. However you can still use return to exit the method under certain conditions:
Future<void> exampleFunction() async{
if (skip) return;
await doSomething();
}
No. The Future type just indicate the function will be called async.

Resources