I have small app that execute requests to external service.
final app = Alfred();
app.post('/ctr', (req, res) async { // complex tender request
var data = await req.body;
await complexTendexAPIRequest(data as Map<String, dynamic>);
print('Hello World');
await res.json({'data': 'ok'});
});
Handler code:
complexTendexAPIRequest(Map<String, dynamic> data) async {
print('Request: $data');
try {
final response = await http.post(
Uri.parse(COMPLEX_URL),
headers: {'Content-Type': 'application/json', 'Authorization': 'bearer $ACCESS_TOKEN'},
body: json.encode(data)
);
if(response.statusCode == 200) {
var res = json.decode(response.body);
int latestId = res['id'];
String url = 'https://api.ru/v2/complex/status?id=$latestId';
stdout.write('Waiting for "complete" status from API: ');
Timer.periodic(Duration(seconds: 1), (timer) async {
final response = await http.get(
Uri.parse(url),
headers: {'Content-Type': 'application/json', 'Authorization': 'bearer $ACCESS_TOKEN'}
);
var data = json.decode(response.body);
if(data['status'] == 'completed') {
timer.cancel();
stdout.write('[DONE]');
stdout.write('\nFetching result: ');
String url = "https://api.ru/v2/complex/results?id=$latestId";
final response = await http.get(
Uri.parse(url),
headers: {'Content-Type': 'application/json', 'Authorization': 'bearer $ACCESS_TOKEN'}
);
stdout.write('[DONE]');
var data = prettyJson(json.decode(response.body));
await File('result.json').writeAsString(data.toString());
print("\nCreating dump of result: [DONE]");
}
});
}
else {
print('[ERROR] Wrong status code for complex request. StatusCode: ${response.statusCode}');
}
}
on SocketException catch(e) {
print('No Internet connection: $e');
} on TimeoutException catch(e) {
print('TenderAPI Timeout: $e');
} on Exception catch(e) {
print('Some unknown Exception: $e');
}
}
But output is very strange it's look like it's do not waiting complexTendexAPIRequest completion and go forward:
Waiting for "complete" status from API: Hello World
[DONE]
Fetching result: [DONE]
Creating dump of result: [DONE]
But should be:
Waiting for "complete" status from API: [DONE]
Fetching result: [DONE]
Creating dump of result: [DONE]
Hello World
I suppose that reason can be in Timer.periodic but how to fix it to get expected order and execution of:
print('Hello World');
await res.json({'data': 'ok'});
only after complexTendexAPIRequest completed.
upd: I rewrote code to while loop:
https://gist.github.com/bubnenkoff/fd6b4f0d7aeae7007680e7902fbdc1e9
it's seems that it's ok.
Alfred https://github.com/rknell/alfred
The problem is the Timer.periodic, as others have pointed out.
You do:
Timer.periodic(Duration(seconds: 1), (timer) async {
// do something ...
});
That sets up a timer, then immediately continues execution.
The timer triggers every second, calls the async callback (which returns a future that no-one ever waits for) and which does something which just might take longer than a second.
You can convert this to a normal loop, basically:
while (true) {
// do something ...
if (data['status'] == 'completed') {
// ...
break;
} else {
// You can choose your own delay here, doesn't have
// to be the same one every time.
await Future.delayed(const Duration(seconds: 1));
}
}
If you still want it to be timer driven, with fixed ticks, consider rewriting this as:
await for (var _ in Stream.periodic(const Duration(seconds: 1))) {
// do something ...
// Change `timer.cancel();` to a `break;` at the end of the block.
}
Here you create a stream which fires an event every second. Then you use an await for loop to wait for each steam event. If the thing you do inside the loop is asynchronous (does an await) then you are even ensured that the next stream event is delayed until the loop body is done, so you won't have two fetches running at the same time.
And if the code throws, the error will be caught by the surrounding try/catch, which I assume was intended, rather than being an uncaught error ending up in the Future that no-one listens to.
If you want to retain the Timer.periodic code, you can, but you need to do something extra to synchronize it with the async/await code around it (which only really understands futures and streams, not timers).
For example:
var timerDone = Completer();
Timer.periodic(const Duration(seconds: 1), (timer) async {
try {
// do something ...
// Add `timerDone.complete();` next to `timer.cancel()`.
} catch (e, s) {
timer.cancel();
timerDone.completeError(e, s);
}
});
await timerDone.future;
This code uses a Completer to complete a future and effectively bridge the gap between timers and futures that can be awaited (one of the listed uses of Completer in the documentation).
You may still risk the timer running concurrently if one step takes longer than a second.
Also, you can possibly use the retry package, if it's the same thing you want to retry until it works.
Related
I'm using the Dart Shelf package and I need to log the response it sends.
I've managed to log the request but the response technique is less clear:
final handler = const shelf.Pipeline()
.addMiddleware(corsHeaders())
.addMiddleware(shelf.logRequests(
logger: (message, isError) =>
_logRequest(message, isError: isError)))
.addHandler((req) async {
final res = await Router().call(req);
return res;
});
There two parts to the question.
how do I log the headers.
is it possible to log the body.
I know there is an issue in that the response body can only be read once.
As some of the responses are likely to be large I need to filter the requests for which the body is logged.
The answer is a bit of Dart-fu. You have an anonymous function returning an anonymous function.
var handler = const Pipeline()
.addMiddleware(
(handler) => (request) async {
final response = await handler(request);
print(response.headers);
// you could read the body here, but you'd also need to
// save the content and pipe it into a new response instance
return response;
},
)
.addHandler(syncHandler);
I have a function defined by:
static Future<http.Response> checkToken(token) async {
return await http.post("my-url", headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
}, body: json.encode({
"token": token
})).timeout(const Duration (seconds:5), onTimeout : () => null);
}
I wonder if this function is identical to this function:
static Future<http.Response> checkToken(token) {
return http.post("my-url", headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
}, body: json.encode({
"token": token
})).timeout(const Duration (seconds:5), onTimeout : () => null);
}
In the second definition I removed async / await part, cause on the web I found this statement:
Tip: If a function returns a Future, it’s considered asyncrounous; you do not need to mark the body of this function with async keyword. The async keyword is necessary only if you have used the await keyword in the body of your function.
Are these two functions identical?
It's true that the async keyword is not what makes a function asynchronous. The two versions of your function therefore are both asynchronous, and they're mostly equivalent but are subtly not identical.
The first version (which uses await) waits for a Future to complete and returns a new Future. The second version just returns the original Future directly.
Let's simplify the example:
final someFuture = Future.value(42);
Future<int> returnsFuture() => someFuture;
Future<int> awaitsFuture() async => await someFuture;
void main() {
final same = identical(returnsFuture(), awaitsFuture());
print('Identical Futures: $same'); // Prints: Identical Futures: false
}
In practice this usually shouldn't matter. Two cases where it might are:
Your code is somehow sensitive to specific Future instances.
await always yields, so the extra await could very slightly delay execution.
Since your asynchronously method does not contain the keyword await, then the two methods is indeed identical.
I made a function to post notification to a topic. It works great in normally, then I put it in compute function and hope it can posts notification in the background. But it not works.
Here is my code:
void onSendMessageInBackGround(String message) {
Future.delayed(Duration(milliseconds: 3000)).then((_) async{
Client client = Client();
final requestHeader = {'Authorization': 'key=my_server_key', 'Content-Type': 'application/json'};
var data = json.encode({
'notification': {
'body': 'tester',
'title': '$message',
},
'priority': 'high',
'data': {
'click_action': 'FLUTTER_NOTIFICATION_CLICK',
'dataMessage': 'test',
'time': "${DateTime.now()}",
},
'to': '/topics/uat'
});
await client.post('https://fcm.googleapis.com/fcm/send', headers: requestHeader, body: data);
});
}
call compute:
compute(onSendMessageInBackGround, 'abc');
Note: I has put the onSendMessageInBackGround function at the top level of my app as the library said
Is it missing something? or we can't do that?
You might need to add a return or await
void onSendMessageInBackGround(String message) {
return /* await (with async above) */ Future.delayed(Duration(milliseconds: 3000)).then((_) async{
It could be that the isolate shuts down before the request is made because you're not awaiting the Future
Function called from compute must be static or global.
Either i agree with pskink, compute here is not usefull.
Isolates communicate by passing messages back and forth. These messages can be primitive values, such as null, num, bool, double, or String, or simple objects such as the List in this example.
You might experience errors if you try to pass more complex objects, such as a Future or http.Response between isolates.
Got this from the documentation here
I need a reusable function that makes an HTTP request and awaits for its completion before returning the response as a String.
Here's the main function:
main() async {
var json;
json = await makeRequest('https://...');
print(json);
print('*** request complete ***');
}
(First Case) This is the reusable function that makes the HTTP request:
makeRequest(String url) async {
var request = await new HttpClient().postUrl(Uri.parse(url));
// Includes the access token in the request headers.
request.headers.add(...);
// Waits until the request is complete.
var response = await request.close();
await for (var contents in response.transform(UTF8.decoder)) {
return contents;
}
}
This works as expected and the output is:
// Response contents as a String...
*** request complete ***
(Second Case) Then I tried to do this and it didn't work:
makeRequest(String url) async {
var request = await new HttpClient().postUrl(Uri.parse(url));
// Includes the access token in the request headers.
request.headers.add(...);
// Waits until the request is complete.
var response = await request.close();
var json = '';
await response.transform(UTF8.decoder).listen((contents) {
// At first I tried to return contents here, but then I added onDone().
json += contents;
}, onDone: () {
return json;
});
return json;
}
I've tried defining the function within listen with async and await, returning contents within listen without onDone(), but the output is the same:
// Empty line.
*** request complete ***
// Waits a few seconds doing nothing before terminating...
Does anyone know why the second case doesn't work?
EDIT:
After updating the code it does what it was supposed to do, but takes a few seconds before terminating execution:
Future<String> twitterRequest(String url) async {
var request = await new HttpClient().postUrl(Uri.parse(url));
// Includes the access token in the request headers.
request.headers.add(...);
// Waits until the request is complete.
var response = await request.close();
var json = '';
await for (var contents in response.transform(UTF8.decoder)) {
json += contents;
// Putting a break here produces the same output but terminates immediately (as wanted).
}
return json;
}
Output:
// Prints response contents...
*** request complete ***
// Takes a few seconds before execution terminates. With the break the code terminates immediately.
EDIT2:
After submitting this issue on GitHub, I found out that instances of the HttpClient have a connection pool and keep persistent connections by default, which keeps the Dart VM alive. Please consult the issue page to find out about the possible solutions.
It's probably caused by the await before the response.transform.
You might want something like
return response.transform(UTF8.decoder).join('');
The pause is not related to makeRequest(). The Dart VM seems to wait for something before it exits. Adding exit(0); as last line in main() makes the application exit immediately.
Update
According to the response on the Dart SDK issue
This is caused by the HttpClient instance having a connection pool
which can keep the Dart VM alive. There are two ways of avoiding this:
1) Close the HttpClient explicitly
2) Use non-persistent connections
import 'dart:async';
import 'dart:convert' show UTF8;
import 'dart:io';
Future main() async {
await makeRequest();
print('end of main');
}
Future makeRequest() async {
var client = new HttpClient();
var request = await client.postUrl(Uri.parse('https://example.com'));
var response = await request.close();
var contents = await response.transform(UTF8.decoder).join();
print(contents);
client.close(); // Close the client.
}
import 'dart:async';
import 'dart:convert' show UTF8;
import 'dart:io';
Future main() async {
await makeRequest();
print('end of main');
}
Future makeRequest() async {
var request = await new HttpClient().postUrl(Uri.parse('https://example.com'));
request.persistentConnection = false; // Use non-persistent connection.
var response = await request.close();
var contents = await response.transform(UTF8.decoder).join();
print(contents);
}
I use my postgres database query to determine my next action. And I need to wait for the results before I can execute the next line of code. Now my conn.query returns a Future but I can't manage to get it async when I place my code in another function.
main() {
// get the database connection string from the settings.ini in the project root folder
db = getdb();
geturl().then((String url) => print(url));
}
Future geturl() {
connect(db).then((conn) {
conn.query("select trim(url) from crawler.crawls where content IS NULL").toList()
.then((result) { return result[0].toString(); })
.catchError((err) => print('Query error: $err'))
.whenComplete(() {
conn.close();
});
});
}
I just want geturl() to wait for the returned value but whatever I do; it fires immediately. Can anyone point me a of a piece of the docs that explains what I am missing here?
You're not actually returning a Future in geturl currently. You have to actually return the Futures that you use:
Future geturl() {
return connect(db).then((conn) {
return conn.query("select trim(url) from crawler.crawls where content IS NULL").toList()
.then((result) { return result[0].toString(); })
.catchError((err) => print('Query error: $err'))
.whenComplete(() {
conn.close();
});
});
}
To elaborate on John's comment, here's how you'd implement this using async/await. (The async/await feature was added in Dart 1.9)
main() async {
try {
var url = await getUrl();
print(url);
} on Exception catch (ex) {
print('Query error: $ex');
}
}
Future getUrl() async {
// get the database connection string from the settings.ini in the project root folder
db = getdb();
var conn = await connect(db);
try {
var sql = "select trim(url) from crawler.crawls where content IS NULL";
var result = await conn.query(sql).toList();
return result[0].toString();
} finally {
conn.close();
}
}
I prefer, in scenarios with multiple-chained futures (hopefully soon a thing of the past once await comes out), to use a Completer. It works like this:
Future geturl() {
final c = new Completer(); // declare a completer.
connect(db).then((conn) {
conn.query("select trim(url) from crawler.crawls where content IS NULL").toList()
.then((result) {
c.complete(result[0].toString()); // use the completer to return the result instead
})
.catchError((err) => print('Query error: $err'))
.whenComplete(() {
conn.close();
});
});
return c.future; // return the future to the completer instead
}
To answer your 'where are the docs' question: https://www.dartlang.org/docs/tutorials/futures/
You said that you were trying to get your geturl() function to 'wait for the returned value'. A function that returns a Future (as in the example in the previous answer) will execute and return immediately, it will not wait. In fact that is precisely what Futures are for, to avoid code doing nothing or 'blocking' while waiting for data to arrive or an external process to finish.
The key thing to understand is that when the interpreter gets to a call to then() or 'catchError()' on a Future, it does not execute the code inside, it puts it aside to be executed later when the future 'completes', and then just keeps right on executing any following code.
In other words, when using Futures in Dart you are setting up chunks of code that will be executed non-linearly.