How to make a await future not last more than 5 seconds ?
I need it because in some networking operation, the connection is sometimes producing silent error. Hence my client just wait for hours with no response. Instead, I want it trigger an error when the clients waits for more than 5 seconds
My code can trigger the error but it is still waiting
Future shouldnotlastmorethan5sec() async {
Future foo = Future.delayed(const Duration(seconds: 10));;
foo.timeout(Duration(seconds: 5), onTimeout: (){
//cancel future ??
throw ('Timeout');
});
await foo;
}
Future test() async {
try{
await shouldnotlastmorethan5sec(); //this shoud not last more than 5 seconds
}catch (e){
print ('the error is ${e.toString()}');
}
}
test();
When you call Future.timeout you need to use the return value to get the correct behaviour. In your case:
Future shouldnotlastmorethan5sec() {
Future foo = Future.delayed(const Duration(seconds: 10));
return foo.timeout(Duration(seconds: 5), onTimeout: (){
//cancel future ??
throw ('Timeout');
});
}
Related
Sample code:
Map<String, String> gg = {'gg': 'abc', 'kk': 'kojk'};
Future<void> secondAsync() async {
await Future.delayed(const Duration(seconds: 2));
print("Second!");
gg.forEach((key, value) async {
await Future.delayed(const Duration(seconds: 5));
print("Third!");
});
}
Future<void> thirdAsync() async {
await Future<String>.delayed(const Duration(seconds: 2));
print('third');
}
void main() async {
secondAsync().then((_) {
thirdAsync();
});
}
output
Second!
third
Third!
Third!
as you can see i want to use to wait until foreach loop of map complete to complete then i want to print third
expected Output
Second!
Third!
Third!
third
Iterable.forEach, Map.forEach, and Stream.forEach are meant to execute some code on each element of a collection for side effects. They take callbacks that have a void return type. Consequently, those .forEach methods cannot use any values returned by the callbacks, including returned Futures. If you supply a function that returns a Future, that Future will be lost, and you will not be able to be notified when it completes. You therefore cannot wait for each iteration to complete, nor can you wait for all iterations to complete.
Do NOT use .forEach with asynchronous callbacks.
Instead, if you want to wait for each asynchronous callback sequentially, just use a normal for loop:
for (var mapEntry in gg.entries) {
await Future.delayed(const Duration(seconds: 5));
}
(In general, I recommend using normal for loops over .forEach in all but special circumstances. Effective Dart has a mostly similar recommendation.)
If you really prefer using .forEach syntax and want to wait for each Future in succession, you could use Future.forEach (which does expect callbacks that return Futures):
await Future.forEach(
gg.entries,
(entry) => Future.delayed(const Duration(seconds: 5)),
);
If you want to allow your asynchronous callbacks to run concurrently (and possibly in parallel), you can use Future.wait:
await Future.wait([
for (var mapEntry in gg.entries)
Future.delayed(const Duration(seconds: 5)),
]);
See https://github.com/dart-lang/linter/issues/891 for a request for an analyzer warning if attempting to use an asynchronous function as a Map.forEach or Iterable.forEach callback (and for a list of many similar StackOverflow questions).
I have a facade function that reloads the current firebase user and returns it. The thing is that the user reloading part has a timeout and it needs to be tested.
Function:
Future<Option<User>> getSignedInUser() async {
// Reload currentUser if possible
// it mustn't throw [TimeoutException] for whole function,
// this is what this try/catch does
try {
await reloadCurrentUser().timeout(const Duration(seconds: 20));
} catch (e) {
log(e.toString(), name: TAG);
}
return optionOf(_auth.currentUser);
}
reloadCurrentUser() function:
Future<Either<AuthFailure, Unit>> reloadCurrentUser() async {
try {
await _auth.currentUser?.reload();
return right(unit);
} catch (e) {
log(e.toString(), name: TAG);
return left(const AuthFailure.userReloadingError());
}
}
The question is how to test reloadCurrentUser() timeout? I'm trying to throw a TimeoutException when this function is called, but then it throws an error for the whole test.
Current Test function:
test(
'Reaches timeout when reloading currentUser, '
'throws TimeoutException, but function continues '
'and returns optionOf currentUser', () async {
reset(fakeFirebaseAuth);
reset(fakeFacebookAuth);
reset(fakeGoogleSignIn);
final currentUser = FakeUser();
// It says that currentUser exists and *IS* authenticated
when(() => fakeFirebaseAuth.currentUser).thenReturn(currentUser);
when(() => firebaseAuthFacade.reloadCurrentUser())
.thenThrow(TimeoutException('timeout', const Duration(seconds: 20)));
final result = await firebaseAuthFacade.getSignedInUser();
expect(result, isA<Some<User>>());
});
Maybe it's better to remove timeout and use some connectivity package to ensure that the user has a network connection and only then reload the current user?
For testing I'm using mocktail package.
You can use the fake_async package.
Here's a simple example from their docs that you can modify for your use case:
import 'dart:async';
import 'package:fake_async/fake_async.dart';
import 'package:test/test.dart';
void main() {
test("Future.timeout() throws an error once the timeout is up", () {
// Any code run within [fakeAsync] is run within the context of the
// [FakeAsync] object passed to the callback.
fakeAsync((async) {
// All asynchronous features that rely on timing are automatically
// controlled by [fakeAsync].
expect(Completer().future.timeout(Duration(seconds: 5)),
throwsA(isA<TimeoutException>()));
// This will cause the timeout above to fire immediately, without waiting
// 5 seconds of real time.
async.elapse(Duration(seconds: 5));
});
});
}
I don't get this behaviour...
main(args) async {
await runZoned(() {
throw false;
}, onError: (e) async {
print("working in onError");
await runZoned(() {
throw false;
}, onError: (e) async {
print("error 1");
});
print("error 2");
});
print("finish");
}
working in onError
error 1
error 2
finish
main(args) async {
await runZoned(() async {
throw await Future.error(false);
}, onError: (e) async {
print("working in onError");
await await runZoned(() async {
throw await Future.error(false);
}, onError: (e) async {
print("error 1");
});
print("error 2");
});
print("finish");
}
working in onError
error 1
those async on runZoned() change the behaviour radically, are they supposed to do so?
I need those asyncs and reaching the print("finish") always. How do I tackle the problem?
You are hitting a sharp edge of futures and error(-handling) zones.
An error thrown in one error zone will not propagate to a future error handler created in another error zone.
So, if you get a future from a different error zone, and it has completed with an error, then you can't get the error, and there is no value to get, so the future will look like it never completes.
The code
main(args) async {
await runZoned(() async {
await Future.error(false);
}, onError: (e) async {
print("working in onError");
await runZoned(() async {
await Future.error(false);
}, onError: (e) async {
print("error 1");
});
print("error 2");
});
print("finish");
}
runs the arguments to runZone in new error zones. They're async functions returning futures, so the the await Future.error(false) will throw and complete the returned future with an error in that particular error zone.
Then, when the runZoned completes, it returns that future with an error created in a different error zone than the call. It is awaited (in the root zone, which is a different error zone) so await runZoned(() async { ... }, ...) never completes. The await waits for the future to complete, the future refuses to give the error that it completed with to the listener, so nothing happens.
You are, effectively, waiting on a future which never completes, which is why your program stops at that point.
So this is working as intended - the error never leaves the error zone it was created in, but that leaves the outer zone without any result.
The first example works because you are throwing synchronously, and not returning a future. The synchronous throw is caught immediately, and then the runZoned returns null. (That will have to change when Dart gets non-nullable types, though).
In general, it's probably a bad idea to return futures that can contain errors from a runZoned with an onError handler. We can't prevent that (returning Object is fine and futures are objects), but it should perhaps be documented.
Thanks to Irn answer I understood and used a Completer for achieving my goal:
main(args) async {
var completer = Completer();
await runZoned(() {
Future.microtask((){
throw Future.error(false);
});
completer.complete();
}, onError: (e) {
// ...
completer.complete();
});
print("finish");
}
I've encountered a weird issue where if I yield* from my provider in my flutter app, the rest of the code in the function doesn't complete.
I'm using the BLoC pattern, so my _mapEventToState function looks like this:
Stream<WizardState> _mapJoiningCongregationToState(
int identifier, int password) async* {
_subscription?.cancel();
_subscription= (_provider.doThings(
id: identifier, password: password))
.listen((progress) => {
dispatch(Event(
progressMessage: progress.progressText))
}, onError: (error){
print(error);
}, onDone: (){
print('done joiining');
});
}
Then in the provider/service... this is the first attempt.
final StreamController<Progress> _progressStream = StreamController<JoinCongregationProgress>();
#override
Stream<JoinCongregationProgress> doThings(
{int id, int password}) async* {
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake1..."));
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake5!!!..."));
yield* _progressStream.stream;
}
The yield statement returns, but only after both awaited functions have completed. This makes complete sense to me, obviously I wouldn't expect the code to complete out of order and somehow run the yield* before waiting for the 'await's to complete.
In order to "subscribe" to the progress of this service though, I need to yield the stream back up to the caller, to write updates on the UI etc. In my mind, this is as simple as moving the yield* to before the first await. Like this.
final StreamController<Progress> _progressStream = StreamController<JoinCongregationProgress>();
#override
Stream<JoinCongregationProgress> doThings(
{int id, int password}) async* {
yield* _progressStream.stream;
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake1..."));
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake5!!!..."));
}
But, then setting breakpoints on the later _progressStream.add calls show that these never get called. I'm stuck on this, any idea what it could be? I know it has something to do with how I have mixed Futures and Streams.
The yield* awaits the completion of the stream it returns.
In this case, you want to return a stream immediately, then asynchronously feed some data into that stream.
Is anything else adding events to the stream controller? If not, you should be able to just do:
#override
Stream<JoinCongregationProgress> doThings({int id, int password}) async* {
await Future.delayed(Duration(seconds:2));
yield JoinCongregationProgress(progressText: "kake1...");
await Future.delayed(Duration(seconds:2));
yield JoinCongregationProgress(progressText: "kake5!!!...");
}
No stream controller is needed.
If other functions also add to the stream controller, then you do need it. You then have to splut your stream creation into an async part which updates the stream controller, and a synchronous part which returns the stream. Maybe:
final StreamController<Progress> _progressStream = StreamController<JoinCongregationProgress>();
#override
Stream<JoinCongregationProgress> doThings({int id, int password}) {
() async {
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake1..."));
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake5!!!..."));
}(); // Spin off async background task to update stream controller.
return _progressStream.stream;
}
Suppose you have an async method body, like below, with a method call that returns a Future and a print following it that doesn't not need the result of that method. Is it possible to add some structure to get the print statement to execute independently? Or does this syntactic sugar force an async method body to wholly run sequentially?
Future<String> test2() {
Completer c = new Completer();
new Timer(new Duration(seconds: 2), () {
c.complete('test2: done');
});
return c.future;
}
Future<String> test1() async {
String result = await test2();
print(result);
// Why is this not executed immediately before test2 call completes?
print('hello');
return 'test1: done';
}
void main() {
test1().then((result) => print(result));
}
Follow up: I've added below a rewrite of test1() which has chained async method calls. I'm really after how to use the async syntactic sugar to simplify this type of use case. How could I rewrite this block with the new syntax?
Future<String> test1() async {
test2().then((result) {
print(result);
test2().then((result) {
print(result);
});
});
// This should be executed immediately before
// the test2() chain completes.
print('hello');
return 'test1: done';
}
edit for follow up:
I am not sure what you want to do after all, but if you want wait for the chain to finish before printing things, here's what you have to do:
Future<String> test1() async {
String result;
var result1 = await test2();
print("Printing result1 $result1 ; this will wait for the first call to test2() to finish to print");
var result2 = await test2();
print("Printing result2 $result2 ; this will wait for the second call to test2() to print");
// This should be executed immediately before
// the test2() chain completes.
print('hello');
return 'test1: done';
}
Now if you call test1() and wait for it to return "test1: done" you have to await it.
main() async {
var result = await test1();
print(result); // should print "hello" and "test1: done"
}
If you want to execute code independently from a previous future result, just don't put the await keyword.
Future<String> test1() async {
test2().then((String result) => print(result)); // the call to test2 will not stop the flow of test1(). Once test2's execution will be finished, it'll print the result asynchronously
// This will be printed without having to wait for test2 to finish.
print('hello');
return 'test1: done';
}
The keyword await makes the flow stalled until test2() is finished.
This is because of the await before test2();. The execution is stalled until the Future returned by test2() is completed.