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.
Related
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.
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. :)
I am using HttpClient to make a request to an api. This code is located in a class library project shared with two aditional projects, a Console and a Asp.Net Mvc project. When I make a request from the Console project it works great, but in the asp project it blocks in the line
using(Stream responseStream = await response.Content.ReadAsStreamAsync()
this is my request code
private async Task<dynamic> ReadJson(string url)
{
HttpResponseMessage response = await httpClient.GetAsync(url);
if (response.StatusCode == System.Net.HttpStatusCode.NoContent)
throw new RateLimitException();
if (response.StatusCode == System.Net.HttpStatusCode.Forbidden)
throw new AccessDeniedException();
if (response.StatusCode != System.Net.HttpStatusCode.OK)
throw new Exception("Error: " + response.StatusCode);
using (Stream responseStream = await response.Content.ReadAsStreamAsync())
using (StreamReader sr = new StreamReader(responseStream, System.Text.Encoding.UTF8))
{
string json = sr.ReadToEnd();
return JObject.Parse(json);
}
}
I am making the same call to the method from the Console and the Asp.Net project. From the console works but the asp .net project blocks in the line when reads the response content
Most probably this deadlock occurs because the controller action that calls ReadJson function is synchronous. You need to make the action async. You can find an excellent explanation of this deadlock here. (All the credits go to Stephen Cleary)
Quick summary is:
/ My "library" method.
public static async Task<JObject> GetJsonAsync(Uri uri)
{
using (var client = new HttpClient())
{
var jsonString = await client.GetStringAsync(uri);
return JObject.Parse(jsonString);
}
}
// My "top-level" method.
public class MyController : ApiController
{
public string Get()
{
var jsonTask = GetJsonAsync(...);
return jsonTask.Result.ToString();
}
}
What Causes the Deadlock
The top-level method calls GetJsonAsync (within the UI/ASP.NET context). GetJsonAsync starts the REST request by calling
HttpClient.GetStringAsync (still within the context).
GetStringAsync returns an uncompleted Task, indicating the REST request is not complete.
GetJsonAsync awaits the Task returned by GetStringAsync. The context is captured and will be used to continue running the
GetJsonAsync method later. GetJsonAsync returns an uncompleted
Task, indicating that the GetJsonAsync method is not complete.
The top-level method synchronously blocks on the Task returned by GetJsonAsync. This blocks the context thread.
… Eventually, the REST request will complete. This completes the Task that was returned by GetStringAsync.
The continuation for GetJsonAsync is now ready to run, and it waits for the context to be available so it can execute in the context.
Deadlock. The top-level method is blocking the context thread, waiting for GetJsonAsync to complete, and GetJsonAsync is waiting for
the context to be free so it can complete.
Preventing the Deadlock
There are two best practices that avoid this situation:
In your “library” async methods, use ConfigureAwait(false) wherever possible.
Don’t block on Tasks; use async all the way down.
Suppose I have an Action like below that I want to return the View asap and continue doing some work in the background thread.
public async Task<ActionResult> Index()
{
Debug.WriteLine("Inside Index");
var newCustomer = new Customer
{
Name = "Ibrahim"
};
Task.Run(() => SaveCustomer(newCustomer));
Debug.WriteLine("Exiting Index");
return View();
}
private async Task SaveCustomer(Customer NewCustomer)
{
Debug.WriteLine("Started Saving Customer");
await Task.Delay(2000);
Debug.WriteLine("Completed Saving Customer");
}
I do get the output as intended which is:
Inside Index
Exiting Index
Started Saving Customer
Completed Saving Customer
But what bothers me is that I get a warning that my Index action will run synchronously regardless and I should put an await but then the view is returned after SaveCustomer is completed and the purpose is defeated.
How am I doing this wrong? Any suggestion is appreciated.
But what bothers me is that I get a warning that my Index action will run synchronously
How am I doing this wrong?
Don't force asynchrony from the top down. Instead, start with naturally-asynchronous operations at the lowest level (e.g., EF6 database access), and allow the asynchrony grow from the lowest-level code upward.
Also, on ASP.NET, you should strongly avoid Task.Run.
Applying these two principles results in an Index method like this:
public async Task<ActionResult> Index()
{
Debug.WriteLine("Inside Index");
var newCustomer = new Customer
{
Name = "Ibrahim"
};
await SaveCustomer(newCustomer);
Debug.WriteLine("Exiting Index");
return View();
}
but then the view is returned after SaveCustomer is completed and the purpose is defeated.
Not at all. The purpose of asynchronous ASP.NET code is not to return early to the client. async and await do not change the HTTP protocol. await on the server side yields to the thread pool, not the client.
If you need to return early (and most people don't - they only think they "need" to), then you should use one of the established patterns for returning early (as I describe on my blog). Note that the only proper (i.e., fully reliable) solution entails setting up a reliable queue with an independent background process.
Your Index does not make use of any async feature at all. Why did you mark it async? You must be misunderstanding something, not sure what. Remove the async Task specification.
You get that compiler warning because there is nothing asynchronous in your Index() method. Your Task.Run(() => SaveCustomer(newCustomer)); line means Fire And Forget (non awaited task) - this is very different than asynchronous code. Index() is completely synchronous, while creating a "side Task" to execute sometime in the future. As the other answer mentioned - you could just as well remove the async mark from your method - it's not 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.
}