Exceptions not propagating up automatically in Dart - dart

Edit:
This was a false alarm. See my answer post below for details.
Original post:
I've been hitting my head against this for a while now, so I made some test code to confirm the behaviour that I'm seeing.
I have this code, where there's a chain of async methods, and one of them deep down is throwing an error.
Future test() async {
await test2();
}
Future test2() async {
await test3();
}
Future test3() async {
await test4();
}
Future test4() async {
throw "a test failure";
}
Then, elsewhere in an async method, I made this call:
try {
await test();
} catch (e) {
print(e);
}
What I'm seeing is that my catch statement isn't being hit.
Now if I instead add a catch within each test method and rethrow the error, instead now the code flows and my topmost catch statement works:
Future test() async {
try {
await test2();
} catch (e) {
print("test");
throw e;
}
}
Future test2() async {
try {
await test3();
} catch (e) {
print("test2");
throw e;
}
}
Future test3() async {
try {
await test4();
} catch (e) {
print("test3");
throw e;
}
}
Future test4() async {
throw "a test failure";
}
What am I missing? Why doesn't my topmost catch statement catch the error unless I explicitly catch and rethrow the error at each level?
For reference, I'm currently running this version of Dart:
Dart SDK version: 2.12.3 (stable) (Wed Apr 14 11:02:39 2021 +0200) on "macos_x64"

Okay, so in the end, it looks like the catch statement was working. The confusion was around how the debugger in VS Code was reacting to it.
When individual try/catch statements are included the debugger flows through the code and ignores the exception. However, when there's only the try/catch at the topmost level, VS Code halts at the exception.
However, if you tell the debugger to continue, it will then hit bubble up to the outer catch statement.
So in the end it's a false alarm and unintuitive behaviour from my debugger.

Related

Difference between throwing and returning a Future.error

Minimal reproducible code
Future<void> foo() async {
final fooError = Future.error('FooError');
return Future.error(fooError);
}
Future<void> bar() async {
await Future(() {});
throw Future.error('BarError');
}
void main() {
foo().catchError((e) => (e as Future).catchError(print)); // Error is NOT handled.
bar().catchError((e) => (e as Future).catchError(print)); // Error is handled.
}
As you can see, BarError is handled but not the FooError. AFAIK, when you mark a method async and use throw, the error is wrapped in a Future.error(...). But my first code doesn't work.
Future.error() creates a Future that completes with an error during a future microtask. This means that if the created Future doesn't have a listener when the microtask is executed, it's considered unhandled and will cause the program to terminate. I'm not 100% sure what's triggering the microtask to be executed, but the microtask queue is always executed and drained until it's empty before executing the next event in the isolate's event loop.
However, those details aren't terribly important. To fix this issue, update your example to not wrap your Future.error('FooError') with another Future.error(...), things work as expected:
Future<void> foo() async {
return Future.error('FooError');
}
Future<void> bar() async {
await Future(() {});
throw 'BarError';
}
void main() {
foo().catchError(print); // Error is now handled.
bar().catchError(print); // Error is handled.
}

Why this reator code with block inside Flux.create couldn't work?

I've tried to use a watchService as a Flux generator and it couldn't work, and I also tried some simple block like Thread.sleep in the Flux.create method and it could work.
I wonder why and what's the difference between these situations?
Code which could work,
#Test
public void createBlockSleepTest() throws InterruptedException {
Flux.create(sink->{
while (true) {
try {
for(int i=0;i<2;i++)
sink.next(num++);
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).log().subscribeOn(Schedulers.parallel(),false).log()
.subscribe(System.out::println);
Thread.sleep(100000L);
}
Code which couldn't work,
#Test
public void createBlockTest() throws IOException, InterruptedException {
WatchService watchService = fileSystem.newWatchService();
Path testPath = fileSystem.getPath("C:/testing");
Files.createDirectories(testPath);
WatchKey register = testPath.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,StandardWatchEventKinds.ENTRY_MODIFY);
Files.write(testPath.resolve("test1.txt"),"hello".getBytes());
Thread.sleep(5000L);
Flux.create(sink->{
while (true) {
try {
WatchKey key = watchService.take();
System.out.println("-----------------"+key);
for(WatchEvent event:key.pollEvents()){
sink.next(event.context());
}
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).log().subscribeOn(Schedulers.parallel(),false).log()
.subscribe(System.out::println);
Files.write(testPath.resolve("test2.txt"),"hello".getBytes());
Thread.sleep(5000L);
Files.write(testPath.resolve("test3.txt"),"hello".getBytes());
Thread.sleep(10000L);
}
I've noticed in the reactor's reference there is a notice for blocking in the create method. But why Thread.sleep works?
create doesn’t parallelize your code nor does it make it asynchronous, even
though it can be used with asynchronous APIs. If you block within the create lambda,
you expose yourself to deadlocks and similar side effects. Even with the use of subscribeOn,
there’s the caveat that a long-blocking create lambda (such as an infinite loop calling
sink.next(t)) can lock the pipeline: the requests would never be performed due to the
loop starving the same thread they are supposed to run from. Use the subscribeOn(Scheduler, false)
variant: requestOnSeparateThread = false will use the Scheduler thread for the create
and still let data flow by performing request in the original thread.
Could anyone solve my puzzle?
This can be fixed by changing
while (true) {
try {
WatchKey key = watchService.take();
System.out.println("-----------------"+key);
for(WatchEvent event:key.pollEvents()){
sink.next(event.context());
}
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
to
while (true) {
try {
WatchKey key = watchService.take();
System.out.println("-----------------"+key);
for(WatchEvent event:key.pollEvents()){
sink.next(event.context());
}
key.reset();
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Thread.sleep(5000L) will only block for 5s, so the create will move on after that delay, whereas WatchService#take blocks indefinitely unless a new WatchKey registers (in this case a new file). Since the code that creates files is after the create, there is a deadlock situation.

Passing Exceptions up the stack in Dart from async to sync methods

So, coming from Javascript world, I can handle exceptions that are thrown however deep down the stack. Doing the same in Dart doesn't work. I'm not sure how to pass exceptions up, to be handled at the root of the stack.
willThrow() async {
throw Exception('Im an exception');
}
init() async {
final illNeverExist = await willThrow();
print(illNeverExist);
}
main() {
try {
init();
} catch(err) {
print(err);
}
}
^^^ This totally works in javascript.
In 'init', even if I wrap that in a try catch, and throw that error, I always get an uncaught exception.
init() async {
try {
final illNeverExist = await willThrow();
print(illNeverExist);
} catch(err) {
throw err
}
}
How do you pass async exceptions up the stack in dart?!
The try-catch block in your main function doesn't wait for your asynchronous init function to complete. Consequently, when init does complete, its exception will no longer be caught.
You can fix this by making main async and using await init();, or you can use Future.catchError to directly register an error callback on the returned Future.

flutter catching errors of nested async methods

I have this method.
asyncFunction1() async {
Firestore.instance.runTransaction((transaction){
var first = await transaction.something;
var second = await secondInside();
});
}
Now I want to call this method, and catch every error that happens inside. How would I propagate errors so that
try {asyncFunction1()}
catch(e){}
catches all errors that happened inside runTransaction?
Your inner function misses an async to be able to use await. If you add it, you can use try/catch
asyncFunction1() {
Firestore.instance.runTransaction((transaction) async {
try {
var first = await transaction.something;
var second = await secondInside();
} catch(e) {
...
}
});
}

How should i use Dart Isolate unhandledExceptionCallback?

I'm trying to use Isolates in my Dart webapp but i can't seem to make the error callback argument work.
I have a very basic code which is being run in Dartium.
import "dart:isolate";
void main() {
print("Main.");
spawnFunction(test, (IsolateUnhandledException e) {
print(e);
});
}
void test() {
throw "Ooops.";
}
I never see anything than "Main." printed on the console. Am i doing something wrong or is this broken right now?
The error-callback will be executed in the new isolate. Therefore it cannot be a dynamic closure but needs to be a static function.
I haven't tested it, but this should work:
import "dart:isolate";
bool errorHandler(IsolateUnhandledException e) {
print(e);
return true;
}
void test() {
throw "Ooops.";
}
void main() {
// Make sure the program doesn't terminate immediately by keeping a
// ReceivePort open. It will never stop now, but at least you should
// see the error in the other isolate now.
var port = new ReceivePort();
print("Main.");
spawnFunction(test, errorHandler);
}
Note: In dart2js this feature is still unimplemented. Older versions just ignored the argument. Newer versions will throw with an UnimplementedError.

Resources