Dart's runZoned - how is it different than async await - dart

So I've read the docs and this example in particular:
runZoned(() {
HttpServer.bind('0.0.0.0', port).then((server) {
server.listen(staticFiles.serveRequest);
});
},
onError: (e, stackTrace) => print('Oh noes! $e $stackTrace'));
I understand the whole zone-local-values thing, and the sort of AOP option of tracing code through a zone - enter/exit.
But other than that, is the code block above any different than:
try {
var server = await HttpServer.bind('0.0.0.0', port);
server.listen(staticFiles.serveRequest);
} catch (e, stackTrace) {
print('Oh noes! $e ${e.stackTrace}');
}
Thanks :)

The runZoned code introduces a new uncaught error handler.
Some asynchronous errors do not happen in a context where they can be thrown to user code. For example, if you create a future, but never await it or listen to it in any other way, and that future then completes with an error, that error is considered "uncaught" or "unhandled". Futures work using callbacks, and there has been no callback provided that it can call.
In that case, the current zone's uncaught error handler is invoked with the error.
Your first example introduces such a handler, which means that if the body of the runZoned call has any uncaught async errors, you'll be told about them.
That includes the future returned by calling then, but also any other asynchronous errors that may happen (if any).
When you await a future, and that future completes with an error, that error is (re)thrown, an can be caught. The try/catch of the second example will catch that error (and any thrown by calling listen, which shouldn't be any as long as server isn't null, which it shouldn't be).
So, the difference is only really in whether uncaught asynchronous errors are handled. (And how any errors in the catch code is propagated, because an error happening during an uncaught-error zone handler will again be uncaught.)

Related

throw Exception('oops!') vs throw 'oops!'

I notices some code that made me think the Exception function call was optional? E.g., do these two lines perform the same function?
throw Exception('oops!');
throw 'oops!'
No.
The former, throw Exception('oops!');, creates a new Exception object using the Exception constructor, then throws that object.
It can be caught by try { ... } on Exception catch (e) { ... }.
The latter, throw 'oops!'; throws the string object.
It can be caught by try { ... } on String catch (e) { ... }.
Generally, you shouldn't be doing either.
If someone made an error, something that would have been nice to catch at compile-time and reject the program, but which happens to not be that easy to detect, throw an Error (preferably some suitable subclass of Error).
Errors are not intended to be caught, but to make the program fail visibly. Some frameworks do catch errors and log them instead. They're typically able to restart the code which failed and carry on, without needing to understand why.
If your code hit some exceptional situation which the caller should be made aware of (and which prevents just continuing), throw a specific subclass of Exception, one which contains the information the caller needs to programmatically handle that situation. Document that the code throws this particular exception. It's really a different kind of return value more than it's an error report. Exceptions are intended to be caught and handled. Not handling an exception is, itself, an error (which is why it's OK for an uncaught exception to also make the program fail visibly).
If you're debugging, by all means throw "WAT!"; all you want. Just remove it before you release the code.

Exception thrown when calling `Completer.completeError`

Please compare the two following examples:
Example 1:
import 'dart:async';
Future<void> main() async {
final c = Completer<void>();
print(1);
c.completeError(Exception("hello"));
print(2);
await c.future;
}
which prints
1
2
Failed to load ".../x_test.dart": hello
Example 2:
import 'dart:async';
Future<void> main() async {
final c = Completer<void>();
print(1);
await (() async => c.completeError(Exception("hello")))();
print(2);
await c.future;
}
which prints
1
Failed to load ".../x_test.dart": hello
I thought that in the second example, the exception should be bound to the future of the Completer and hence also be thrown when awaiting for the future.
Is there a way to defer an exception with a Completer to a later point?
What I see when I run your second example in DartPad is
1
Uncaught Error: Exception: hello
2
Uncaught Error: Exception: hello
The error is uncaught twice. That matches what is actually happening:
You print 1.
You call () async { c.complete(Exception("hello")); }(). This schedules a microtask to
complete the completer's future with the exception. Then the call returns another future and schedules a microtask to complete that future with null.
Then you await the latter future.
Then the first microtask runs and completes the completer's future with an error. At this point there are no listeners on the future, so the error is considered uncaught and reported to the root zone's uncaught async error handler. This prints the first uncaught error line.
Then the second microtask runs and completers the other future. This makes the await complete.
Then you print 2.
Then you await the completer's future. Since the future is already complete, this schedules a microtask report this to the await.
Then that microtask runs and the second await completes by throwing the exception of the completer's future.
That throw is uncaught and is therefore also reported as an uncaught error.
It's no entirely clear what you want. Do you want the completer's future to complete later (if so, how much), or do you just not want the first completion to be considered uncaught?
The latter is easier, just do:
completer..future.catchError((_){})..complete(Exception("hello"));
If you pre-attach an error handler to the future, which handles (by ignoring) the error, then the future won't consider the error unhandled.
(Arguably, it would be nice to have a way to complete a completer with an error, and say up front that it shouldn't be considered unhandled).

How to globally catch unhandled Future errors?

Best practice is of course to always attach an error handler to a Future using catchError() (or using await and try/catch). But suppose I forgot, or suppose that this error is serious enough that we want it to crash the entire application (as we could do with synchronous exceptions), or that I want to log the error, or report it to some service like Crashlytics to make me aware of my sins.
Dart's Futures are practically the same as JavaScript's Promises. In NodeJS, we can attach a handler to the global unhandledRejection event to add custom behaviour.
Does Dart offer something similar? I looked through the async and Future documentation, but couldn't find anything relevant.
Take a look at the runZonedGuarded static method. It will executing a given method in its own Zone which makes it possible to attach a method to handle any uncaught errors.
I have made a simple example here which shows what happens if a async error are throw without any handling of the error:
import 'dart:async';
import 'dart:io';
void main() {
runZonedGuarded(program, errorHandler);
}
Future<void> program() async {
final file = File('missing_file.txt');
await file.openRead().forEach(print);
}
void errorHandler(Object error, StackTrace stack) {
print('OH NO AN ERROR: $error');
}
Which returns:
OH NO AN ERROR: FileSystemException: Cannot open file, path = 'missing_file.txt'...

Difference between 'catch(e)' and 'on Exception catch(e)' in dart?

What is the difference between catch(e) and on Exception catch(e) in dart?
AVOID catches without on clauses.
Using catch clauses without on clauses makes your code prone to encountering unexpected errors that won't be thrown (and thus will go unnoticed).
BAD:
try {
somethingRisky()
} catch(e) {
doSomething(e);
}
GOOD:
try {
somethingRisky()
} on Exception catch(e) {
doSomething(e);
}
Link: avoid_catches_without_on_clauses
The } on Exception catch (e) { will catch all thrown objects implementing Exception.
The excludes most errors (which implement Error),
The } catch (e) { will catch all thrown objects, both exceptions and errors - and anything else that might get thrown. Most thrown objects implement either Exception or Error, but that's just a convention. Any non-null object can be thrown.
I'd actually recommend against on Exception as well. Exceptions are not errors, they are intended for functions as an alternative to returning a value, but exceptions are still just as much part of the function API, and you should only be catching the exceptions that you are actually planning to handle. Since Exception itself has no information, you should be catching the subtype that the function is documented as throwing so you can use the available information to handle the exceptional case.
If you are not going to handle it, you might as well treat the exception as an error.
Using only } catch (e) { to catch everything is reasonable in some situations, mainly in framework code which wraps other user code, and needs to make sure a user code error or unhandled exception doesn't take down the entire program.

Invoking async function without await in Dart, like starting a thread

I have two functions
callee() async {
// do something that takes some time
}
caller () async {
await callee();
}
In this scenario, caller() waits till callee() finishes. I don't want that. I want caller() to complete right after invoking callee(). callee() can complete whenever in the future, I don't care. I just want to start it just like a thread and then forget about it.
Is this possible?
When you call the callee function, it returns a Future. The await then waits for that future to complete. If you don't await the future, it will eventually complete anyway, but your caller function won't be blocked on waiting for that. So, you can just do:
caller() {
callee(); // Ignore returned Future (at your own peril).
}
If you do that, you should be aware of what happens if callee fails with an error. That would make the returned future complete with that error, and if you don't listen on the future, that error is considered "uncaught". Uncaught errors are handled by the current Zone and the default behavior is to act like a top-level uncaught error which may kill your isolate.
So, remember to handle the error.
If callee can't fail, great, you're done (unless it fails anyway, then you'll have fun debugging that).
Actually, because of the risk of just forgetting to await a future, the highly reocmmended unawaited_futures lint requires that you don't just ignore a returned future, and instead wants you to do unawaited(callee()); to signal that it's deliberate. (The unawaited function can be imported from package:meta and will be available from the dart:async library in SDK version 2.14).
The unawaited function doesn't handle errors though, so if you can have errors, you should do something more.
You can handle the error locally:
caller() {
callee().catchError((e, s) {
logErrorSomehow(e, s);
});
}
(Since null safety, this code only works if the callee() future has a nullable value type. From Dart 2.14, you'll be able to use callee().ignore() instead, until then you can do callee().then((_) => null, onError: (e, s) => logErrorSomehow(e, s)); instead.)
or you can install an error handling zone and run your code in that:
runZoned(() {
myProgram();
}, onError: logErrorSomehow);
See the runZoned function and it's onError parameter.
Sure, just omit await. This way callee() is called immediately and when an async operation is called the call will be scheduled in the event queue for later execution and caller() is continued immediately afterwards.
This isn't like a thread though. As mentioned processing is enqueued to the event queue which means it won't be executed until the current task and all previously enqueued tasks are completed.
If you want real parallel execution you need to utilize isolates.
See also
https://www.dartlang.org/articles/event-loop/
https://api.dartlang.org/stable/1.16.1/dart-isolate/dart-isolate-library.html
https://pub.dartlang.org/packages/isolate

Resources