Extending a Future and/or preserving await functionality - dart

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.

Related

How to get Redis key values on Server side Dart with Angel

Having moved my mobile app development to Flutter I am now in the process of experimenting with using Dart as my main server side language. The productivity benefits in using a single coding language in both the app and on the server are considerable. To that end I have set up a server with an Nginx front end which proxies all dynamic web requests to an Angel/Dart server.
Angel is a remarkably well written package and I had a working server written up in no time at all. However, in order to have a fully functional backend I need to be able to use both Redis and PostgreSQL from within my server side Dart code. I am using the resp_client package to access Redis. The issue I have run into is with the fact that RespCommand.get is asynchronous. With my newbie knowledge of both Dart and Angel I am unable to find a way to acquire a Redis key value via RespCommand.get in an Angel route handler and then somehow use that value in the response it returns.
My entire Dart backend server code is shown below
import 'package:angel_framework/angel_framework.dart';
import 'package:angel_framework/http.dart';
import 'package:postgres/postgres.dart';
import 'package:resp_client/resp_client.dart';
import 'package:resp_client/resp_commands.dart';
class DartWeb
{
static Angel angel;
static AngelHttp http;
static RespCommands redis;
static PostgreSQLConnection db;
static init() async
{
angel = Angel();
http = AngelHttp(angel);
angel.get('/',rootRoute);
await prepareRedis();
await http.startServer('localhost',3000);
}
static prepareRedis() async
{
RespServerConnection rsc = await connectSocket('localhost');
RespClient client = RespClient(rsc);
redis = RespCommands(client);
}
static preparePostgres() async
{
db = new PostgreSQLConnection('serverurl',portNo,'database',username:'user',password:'password');
await db.open();
}
static void rootRoute(RequestContext req,ResponseContext res)
{
try
{
await redis.set('test','foobar',expire:Duration(seconds:10));
String testVal = await redis.get('test');
res.write('Done $testVal');
} catch(e) {res.write('++ $e ++');}
}
}
main() async {await DartWeb.init();}
If I start up this server and then access it through my web browser I end up with a 502 Bad Gateway message. Not surprising. dart2native main.dart -o mainCompiled returns the error await can only be used in async... message.
So I tried instead
try
{
res.write('Before');
redis.set('test','foobar',expire:Duration(seconds:10)).then((bool done)
{
res.write('DONE $done');
});
res.write('After');
} catch(e) {res.write('++ $e ++');}
which simply printed out BeforeAfter in my browser with the DONE bit never showing up although a quick test via redis-cli shows that the key test had in fact been created.
My knowledge of both Dart and Angel is still in its infancy so I guess I am doing something incorrectly here. Shorn of all the detail my questions are essentially these -
how do I call and get the result from async methods in an Angel route dispatcher?
given that I am editing my Dart code in VSCode on my local Windows machine which accesses the relevant dart files on my Ubuntu server I loose the benefits of error reporting provided by the VSCode Dart plugin. dart2native, as I have used here, helps out but it would be nicer if I could somehow get a running error report within VSCode as I do when building Flutter apps locally. How can I accomplish this - if at all possible?
It turns out that Dart/Angel does not impose excessively strict constraints on the signature of a route handler. So you can quite safely declare a route handler like this one
static Future<void> rootRoute(RequestContext req,ResponseContext res) async
{
try
{
res.write('!! Before ');
await redis.set('test','foobar',expire:Duration(seconds:10));
String test = await redis.get('test');
res.write('After $test !!');
} catch(e) {res.write('++ $e ++');}
}
With the route simply returning a Future we can now safely do anything we like there - including calling other asynchronous methods: in this instance to fetch a Redis key value.

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

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. :)

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.

Spring Integration DSL: lambda to return a Message<T> in handle method, e.g. with DelegatingSessionFactory?

Motivation: I need to set the threadKey for a DelegatingSessionFactory before I route to an Sftp outbound gateway and unset the threadKey afterwards.
Depending on a tenant I need to use a different Sftp user account. The user accounts are a matter of configuration in my application.yml, I do not want to write separate routes for each new tenant.
public IntegrationFlow aDynamicSftpFlow() {
f -> f
.handle(tenantSessionDefine()) // how can I use a lambda instead?
.handle(Sftp.outboundGateway(delegatingSessionFactory, ...))
.handle(...) // undefine sftp session
}
Setting the threadKey requires a Message<?>, not just payload and headers. So I use a bean because it takes a message:
public class TenantSessionDefine {
private DelegatingSessionFactory delegatingSessionFactory;
public TenantSessionDefine(DelegatingSessionFactory delegatingSessionFactory) {
this.delegatingSessionFactory = delegatingSessionFactory;
}
public Message<?> defineSession(Message<?> message) {
return delegatingSessionFactory.setThreadKey(message, message.getHeaders()
.get("tenantId", String.class));
// used by SessionFactoryLocator
}
}
I would like to write that as a lambda, as in
.handle(message -> delegatingSessionFactory.setThreadKey(message,
message.getPayload().getTenant())
but that is not so easy. The lambda that can be used with handle() which takes a Message<T> ends the flow because it is a void function (MessageHandler functional interface). The other lambda is a GenericHandler, which does not end the flow, but it takes payload and headers, not a message.
This is just an example, every now and then I wish I could use handle() with a message in a lambda without ending the flow. How can I do that?
Update
The DelegatingSessionFactory is not a particularly well suited example. Since setting and clearing the thread key should happen before and after the sftp invocation, an advice fits better than defining a handler before and after the call.
Got it. The javadoc for handle() says
Use handle(Class, GenericHandler) if you need to access the entire message.
The Class parameter must be Message.class:
.handle(Message.class,
(message, headers) -> sftpSessionFactory
.setThreadKey(message, headers.get("tenantId")))

Run method on a separate thread inside Action

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.

Resources