efficient async function that needs result from another async function in dart (http client) - dart

From here Dart - Request GET with cookie we have this example of doing a get request with dart's built in HTTP library:
exampleCall() {
HttpClient client = new HttpClient();
HttpClientRequest clientRequest =
await client.getUrl(Uri.parse("http: //www.example.com/"));
clientRequest.cookies.add(Cookie("sessionid", "asdasdasqqwd"));
HttpClientResponse clientResponse = await clientRequest.close();
}
As you can see, multiple awaits are needed. Which means that if I try to do multiple concurrent exampleCall calls, they won't happen at the same time.
I cannot return a future because I must wait the client.getUrl() in order to do the clientResponse.
I also couldn't find a good alternative to use cookies on http calls. Dio seems to only support storing cookies from the server. Anyways, I'd like to know how to do in this way, but if there's a better way I'd like to know.

As you can see, multiple awaits are needed. Which means that if I try to do multiple concurrent exampleCall calls, they won't happen at the same time.
Not really sure what you mean here. Dart is single threaded so the concept of things happen "at the same time" is a little vauge. But if you follow the example later you should be able to call exampleCall() multiple times without the need of waiting on each other.
I cannot return a future because I must wait the client.getUrl() in order to do the clientResponse.
Yes you can if you mark the method as async:
import 'dart:convert';
import 'dart:io';
Future<List<String>> exampleCall() async {
final client = HttpClient();
final clientRequest =
await client.getUrl(Uri.parse("http://www.example.com/"));
clientRequest.cookies.add(Cookie("sessionid", "asdasdasqqwd"));
final clientResponse = await clientRequest.close();
return clientResponse
.transform(utf8.decoder)
.transform(const LineSplitter())
.toList();
}
The whole point of async methods is the ability to easily bundle multiple asynchronous calls into a single Future. Notice, that async methods must always return a Future but your return statement should not necessarily return a Future object (if you return a normal object, it will automatically be packed into a Future).
I also couldn't find a good alternative to use cookies on http calls. Dio seems to only support storing cookies from the server. Anyways, I'd like to know how to do in this way, but if there's a better way I'd like to know.
Not really sure about the whole cookie situation. :)

Related

dart compute Illegal argument in isolate message

I am using compute to do some work while keeping the UI running. The compute was working until I added another http call before it.
The working code is as follow
final ListRequest request =
ListRequest(baseUrl: env['SERVER_URL']!, path: '/Items');
_mainController.updateListItems(
await compute(_service.getItems, request));
I read some articles saying the function compute calls should be a top level function or a static function. However, the getItems is an instance function and there was no exception.
Recently I added a few lines and the code became
final Filter? filter = await _service.getFilter();
final ListRequest request =
ListRequest(baseUrl: env['SERVER_URL']!, path: '/Items');
request.filter = filter;
_mainController.updateListItems(
await compute(_service.getItems, request));
getFilter is a http call to retrieve some filter parameters from the backend.
Then I got the following error
Invalid argument(s): Illegal argument in isolate message: (object extends NativeWrapper - Library:'dart:io' Class: _SecureFilterImpl#13069316)
My dart and flutter versions are
Dart SDK version: 2.15.1 (stable)
Flutter 2.8.1
Thank you
=========================================================
Update
The Filter is
Filter {
String? itemLocationSuburb;
String? itemLocationPostcode;
}
Your _service service presumably contains a HttpClient. When you make a request through this client, it opens a connection to the HTTP server, and may maintain the connection after the request completes.
The HttpClient cannot be sent through a SendPort when it has open connections, but it is included in the scope of the getItems method.
To work around this issue, you can do one of the following:
Disable persistent connections with the HttpClientRequest.persistentConnection property
Make a new HttpClient to send through the compute function every time
Implement a long-lived background isolate to maintain its own HttpClient
Use the HttpClient in the main isolate, and only perform other work like parsing with compute (there's no significant benefit to using an isolate to make HTTP requests anyway)

Extending a Future and/or preserving await functionality

I'd like to extend the Future class and give it more functionality while keeping the functionality of the await keyword. As I understand it, Futures can't be extended directly in Dart, but perhaps there is another way to achieve what I want?
The class I'm trying to create is effectively a mechanism for interacting with an API. The server is RPC-like in the sense that its' API can be bi-directional while the request is open (messages can go back and forth between server and client until the request is considered resolved).
To make the client library more usable, I'm trying to create a Request class that has all the goodness of a normal Future, but also the ability to use a when() or on() function which effectively listens for updates during the resolution of the future.
Some sudo code of usage:
Request(args)
.when('SomeEvent', (event) => print('An update: $event'))
.then((response) => print('The final response: $response'))
.onError((err) => print('Some error: $err'));
// This also needs to work:
final response = await Request(args);
So far I have something like this, but the await keyword doesn't work:
class Request {
final Completer _completer = Completer();
Request(args) {
/* Setup and make some request to an API and respond/update using response|error|update */
}
Future<dynamic> then(fn) async {
// Should this actually return a Request?
return _completer.future.then(fn);
}
Future<dynamic> onError(fn) async {
// Should this actually return a Request?
return _completer.future.onError(fn);
}
Request when(String eventName, Function fn) {
/* attach a listener/stream which fires fn on update */
return this;
}
void _response(res) {
_completer.complete(res);
}
void _error(err) {
_completer.completeError(err);
}
void _update(upd) {
/* Some update from the request is given */
}
}
Is what I'm attempting impossible in Dart?
I'd recommend not extending the Future interface, but instead let your Request class have a future instead of being a future.
Then you can do await request.result and request.when(...), without having to re-implement the entire Future API.
If you insist on making Request be a Future, all you need is to add implements Future<Object?> to the class ... and then actually implement the entire Future API. No need to do onError (that's an extension method which works on any Future, including your Request), but you need to implement then, catchError, whenComplete, asStream and timeout correctly and totally (support all the arguments and have the correct type).
Then you'll be able to use your class with await.
If you do that, you can make those functions return Request too, if you make Request generic (class Request<T> implements Future<T>), because .then<int>(...) needs to return a Future<int>. You'd need a strategy for forwarding the when/on events to the new futures then.
It's much easier not to do that, and just expose the internal future by itself, separately from the progress callbacks.
See also CancelableOperation.

Blazor-server scoped services, closed connections, garbage collection

If I have a scoped service:
services.AddSingleton<MyScopedService>();
And in that service, an HTTP request is made:
HttpClient client = _clientFactory.CreateClient();
StringContent formData = ...;
HttpResponseMessage response = await client.PostAsync(uri, formData);
string data = await response.Content.ReadAsStringAsync();
I read here that for an AddScoped service, the service scope is the SignalR connection.
If the user closes the browser tab before the response is returned, the MyScopedService code still completes.
Could someone explain what happens to that MyScopedService instance? When is it considered out of scope? After the code completes? Is the time until it's garbage collected predictable?
I have a Blazor-server project using scoped dependency injections (fluxor, and a CircuitHandler), and I'm noticing that the total app memory increases with each new connection (obviously), but takes a while (minutes) for the memory to come down after the browser tabs are closed.
Just wondering if this is expected, or if I could be doing something to let the memory usage recover more quickly. Or maybe I'm doing something wrong with my scoped services.
Add IDisposeAsync to your service then in your service :
public async ValueTask DisposeAsync() => await hubConnection.DisposeAsync();
This was copied from one of my own libraries I was facing the same issue. GC will not work if there are references to other objects...

How to make a nested httpClient call, multiple async / awaits in Dart

I have to make a few calls using httpClient. One of them gets the 'main' data for a given blog post in json format, and with that info I then have to make a second call for additional media info related to that post.
This is all for multiple posts, so in essence I'm doing:
Future<List<String>> fetchPosts() async {
response = await httpClient.get('http://somewebsite/topPosts');
data = json.decode(response.body) as List;
data.map((singlePost) {
mediaID = singlePost["mediaID"];
//second await call below, this won't work as-is, correct?
finalData = await httpClient.get('http://somewebsite/media/$mediaID')
I hope that makes sense, what I'm trying accomplish. Essentially I'm nesting http calls. Not sure of a 'good' way of doing this, maybe something with Future.wait(), any advice welcome.
data.map((singlePost) {
mediaID = singlePost["mediaID"];
//second await call below, this won't work as-is, correct?
finalData = await httpClient.get('http://somewebsite/media/$mediaID')
Isn't going to work. First, you're using await in the callback supplied to map, but await can be used only in a function marked async.
After you mark the callback with async, the code probably doesn't do what you want. List.map() is used to transform a List. For example, you'd use it to create a list of Strings from a list of ints or vice-versa. In this case, you'll be generating a list (actually an Iterable) of Futures. You don't store that list of Futures anywhere, and therefore you can't ever await them to be notified when they complete.
What you might is something like:
final futures = data.map((singlePost) async { ... });
await Future.wait(futures);
And that would wait for all operations to complete. However, you'll have another problem in that your map callback does:
finalData = await httpClient.get('http://somewebsite/media/$mediaID')
which means that each callback will clobber the finalData variable (whatever that is) in some unspecified order.

Is an MVC Async controller that calls WebResponse still async?

We have a large library that makes a lot of HTTP calls using HttpWebRequest to get data. Rewriting this library to make use of async calls with the HTTPClient would be a bear. So, I was wondering if I could create async controllers that use a taskfactory to call into our library and whether the calls that are ultimately made via the WebClient would be asynch or they would still be synch. Are there any problems/side-effects I might cause by trying to mix async with the old HttpWebRequest?
If I'm understanding what you're proposing the answer is: no, changing the services the client talks to to be async would not help. The client would still block a CPU thread while the I/O is outstanding with the server, whether the server is async or not.
There's no reason to switch away from HttpWebRequest. You can use TaskFactory::FromAsync in .NET 4.0 to call HttpWebRequest::BeginGetResponse. That looks something like this:
WebRequest myWebRequest = WebRequest.Create("http://www.stackoverflow.com");
Task<WebResponse> getResponseTask = Task<WebResponse>.Factory.FromAsync(
myWebRequest.BeginGetResponse,
myWebRequest.EndGetResponse,
null);
getResponseTask.ContinueWith(getResponseAntecedent =>
{
WebResponse webResponse = getResponseAntecedent.Result;
Stream webResponseStream = webResponse.GetResponseStream();
// read from stream async too... eventually dispose of it
});
In .NET 4.5 you can still continue to use HttpWebRequest and use the new GetResponseAsync method with the new await features in C# to make life a heck of a lot easier:
WebRequest myWebRequest = WebRequest.Create("http://www.stackoverflow.com");
using(WebResponse webResponse = await myWebRequest.GetResponseAsync())
using(Stream webResponseStream = webResponse.GetResponseStream())
{
// read from stream async, etc.
}

Resources