In old-fashioned sync code, you can always assure your program won't crash completely by encapsulation your source code to the one big try catch block as in example:
try {
// Some piece of code
} catch (e) {
logger.log(e); // log error
}
However in Dart, when using Futures and Streams, it is not so easy. Following example will crash your application completely
try {
doSomethingAsync().then((result) => throw new Exception());
} catch (e) {
logger.log(e);
}
It doesn't matter that you have code inside the try-catch block.
Yes, you can always use Future.catchError, unfortunately, this won't help you if you are using third-party library function as following:
void someThirdPartyDangerousMethod() {
new Future.value(true).then((result) => throw new Exception());
}
try {
// we can't use catchError, because this method does not return Future
someThirdPartyDangerousMethod();
} catch (e) {
logger.log(e);
}
Is there a way to prevent the untrusty code to break whole your application? Something like global error handler?
You can use the brand new Zones. Just run your code inside the Zone and attach error handler to it.
void someThirdPartyDangerousMethod() {
new Future.value(true).then((result) => throw new Exception());
}
runZoned(() {
// we can't use catchError, because this method does not return Future
someThirdPartyDangerousMethod();
}, onError: (e) {
logger.log(e);
});
This should just work as expected! Every uncatched error will be handled by the onError handler. One thing is different to the classical example with try-catch block. The code running inside the Zone won't stop when error occurs, error is handled by onError callback and the application continues.
Related
I've enabled the dart 2.8 experimental null saftey.
I have the following exiting code.
StreamSubscription<String> subscription;
subscription =
response.transform(Utf8Decoder()).transform(LineSplitter()).listen(
(line) async {
result += line;
},
onDone: () async {
unawaited(subscription.cancel());
completer.complete(result);
},
);
With null saftey enabled I get a error in the 'onDone' method where it calls subscription.cancl
"The expression is nullable and must be null-checked before it can be used.
Try checking that the value isn't null before using it.",
I can fix the problem by putting a conditional before the call to cancel, but this seems unnecessary as in reality subscription can never be null.
Is there a coding pattern that allows subscription to be declared as non-null?
The problem here is that the read of subscription happens at a place where it's still potentially unassigned. It isn't, actually, but we only know that because the listen method promises not to call any of the callbacks before returning. The compiler can't see that. So, you need to move the reading to after the assignment.
What I'd do to make this listen call work:
var buffer = StringBuffer(result);
var subscription = response
.transform(Utf8Decoder())
.transform(LineSplitter())
.listen((line) {
buffer.write(line);
});
subscription.onDone(() {
completer.complete(buffer.toString());
});
I removed the async from the callbacks because it is not needed. All it does to make these functions async is to return a future that no-one would ever look at.
In general, the callbacks on Stream and Future should have non-async callbacks.
I also removed the subscription.cancel from the onDone event handler. If you get a "done" event, the subscription is done, there is no need to cancel it.
I also added a string buffer to avoid the quadratic time and space complexity of repeated string concatenation.
Looking at the code, you seem to be concatenating lines right after splitting them, maybe all you need is:
response.transform(Utf8Decoder()).join("").then(completer.complete);
I'll assume for now that the splitting+joining is necessary.
In that case, what I'd actually prefer to do instead is of using listen is:
var buffer = StringBuffer();
response
.transform(Utf8Decoder())
.transform(LineSplitter())
.forEach((line) {
buffer.write(line);
}).then(() {
completer.complete(buffer.toString());
}, onError: (e, s) {
completer.completeError(e, s);
});
or, if in an async function:
try {
var buffer = StringBuffer();
await for (var line in response.transform(Utf8Decoder()).transform(LineSplitter())) {
buffer.write(line);
}
completer.complete(buffer.toString());
} catch(e, s) {
completer.completeError(e, s);
}
Is it possible to block a function call that returns a future?
I was under the impression calling .then() does it, but that's not what I'm seeing in my output.
print("1");
HttpRequest.getString(url).then((json) {
print("2");
});
print("3");
What I'm seeing in my output is:
1
3
2
The getString method doesn't have an async that would allow me to await it and then executes asynchronously in any case.
static Future<String> getString(String url,
{bool withCredentials, void onProgress(ProgressEvent e)}) {
return request(url, withCredentials: withCredentials,
onProgress: onProgress).then((HttpRequest xhr) => xhr.responseText);
}
How do I make it blocking without placing an infinite while loop before step 3 waiting for step 2 to be completed (not that it would work anyways due to the single thread nature of Dart)?
The above HttpRequest loads a config.json file that determines how everything works in the app, if the request for a field in the config is done before the config.json file is done loading, it causes errors, so I need to wait until the file is done loading before I allow calling getters on the fields of the class or getters needs to wait for the once-off loading of the config.json file.
Update, this is what I eventually did to make it work after Günter suggested I use a Completer:
#Injectable()
class ConfigService {
Completer _api = new Completer();
Completer _version = new Completer();
ConfigService() {
String jsonURI =
"json/config-" + Uri.base.host.replaceAll("\.", "-") + ".json";
HttpRequest.getString(jsonURI).then((json) {
var config = JSON.decode(json);
this._api.complete(config["api"]);
this._version.complete(config["version"]);
});
}
Future<String> get api {
return this._api.future;
}
Future<String> get version {
return this._version.future;
}
}
And where I use the ConfigService:
#override
ngAfterContentInit() async {
var api = await config.api;
var version = await config.version;
print(api);
print(version);
}
Now I get blocking-like functionality without it actually blocking.
There is no way to block execution until asynchronous code completes. What you can do is to chain successive code so that it is not executed before the async code is completed.
One way to chain is then
print("1");
HttpRequest.getString(url) // async call that returns a `Future`
.then((json) { // uses the `Future` to chain `(json) { print("2"); }`
print("2");
});
print("3"); // not chained and therefore executed before the `Future` of `getString()` completes.
An async call is just scheduling code for later execution. It will be added to the event queue and when the tasks before it are processed it itself will be executed. After an async call is scheduled the sync code `print("3") is continued.
In your case HttpRequest.getString() schedules a call to your server and registers (json) { print("2") as callback to be called when the response from the server arrives. Further execution of the application doesn't stall until the response arrives and there is no way to make that happen. What instead happens is that sync code is continued to be executed (print("3")).
If your currently executed sync code reaches its end, then the next scheduled task is processed the same way.
then() schedules the code (json) { print("2"); } to be executed after getString() completed.
await
async and await just make async code look more like sync code but otherwise it is quite the same and will be translated under the hood to xxx.then((y) { ... }).
I'm not sure I understand what you're trying to achieve, but it looks to me you want to do this:
myFunction() async {
print("1");
final json = await HttpRequest.getString(url);
print("2");
print("3");
}
async statement is only needed in the consumer function. In other words, producer functions doesn't need to have async, they only need to return a Future.
you should be able to do this:
Future consumerFunc() async {
print("1");
var response = await HttpRequest.getString(url);
print("2");
print("3");
}
and it should result:
1
2
3
Note: await replaces then methods
I have a Dart function that looks like:
Future beAwesome() {
if (notActuallySupported) {
return new Future.error(new UnsupportedError('uh oh'));
}
return new Future.value(42);
}
// ...
beAwesome().then((answer) => print(answer));
I want to use the new async/await functionality. How do I change my function?
In general, add the word async after your function's signature and before the {. Also, return raw values instead of wrapping those values in futures. Also, throw actual exceptions instead of wrapping the errors with a future.
Here's the new version:
Future beAwesome() async {
if (notActuallySupported) {
throw new UnsupportedError('uh oh');
}
return 42;
}
// ...
var answer = await beAwesome();
print(answer);
Note that you should still use Future as the return-type annotation.
I defined some class to query a database.
class SqlGetData {
ConnectionPool pool;
List<String> rows;
SqlGetData(this.pool);
Future <List<String>> run(String sQuery) {
rows = new List<String>();
return readData(sQuery).then((_) {
return rows;
});
}
Future readData(String sQuery) {
return pool.query(sQuery).then((result) {
return result.forEach((row) {
String s = JSON.encode(row);
rows.add(s);
});
});
}
}
which I call like this:
var sql = new SqlGetData(pool);
sql.run('select firstName, lastName from person where id =' + s1).then((rows) {
some code here to process the data
});
If the database is not running I get an error on the return pool.query in readData, which I want to catch and pass to the client in some error message.
How and where can I code the try ... catch ... to prevent the server from dying? My problem is that I have to return futures, which is still difficult for me to grasp.
Take a look at this article Futures and Error Handling (if you haven't already).
There are two places:
.then((_) => doSomething(),
onError: (e) => doErrorHandling()).catchError((e) => doErrorHandling());
Guenter's answer is good. Here are a couple of extra tips.
It's more common to use .catchError() than the named parameter, if in doubt just use .catchError().
One problem with async code is if you forget to add a catchError handler anywhere in your codebase, and an error is triggered, it will bring your whole server down. Not good. However You can use Zones to handle uncaught errors in your code, and prevent this from happening.
There isn't much documentation about Zones at the time of writing, as it is a new feature. Florian Loitsch is working on an article which will appear here sometime soon. Here is an example of using runZoned():
runZoned(() {
var pool = new Pool.connect(...); // Don't know pool API, just making this up.
pool.query(sql).then((result) {
print(result);
});
// Oops developer forgot to add a .catchError() handler for the query.
// .catchError((e) => print('Query error: $e);
}, onError: (e) => print("Uncaught error: $e"));
This code will run without bringing down your server, despite the missing catchError() handler. Note, you will need to start the pool/connection within the same zone as the query is executed within.
I'm trying to implement exception handling for Optimistic lock type exceptions that are thrown by Hibernate but I've encountered a strange issue. It seems I'm unable to catch any Gorm exceptions.
For example I have this code in my service:
try {
User user = User.get(1);
Thread.sleep(10000);
user.viewedAt(new Date());
user.save(flush:true);
} catch (OptimisticLockingException ex) {
log.error("Optimistic lock exception");
} catch (StaleObjectStateException ex) {
log.error("Optimistic lock exception");
}
When I hit this block with two threads, it blows up and the exception propagates to Grails' standard exception handler. The catch blocks are never invoked even though the reported exception is StaleObjectStateException.
I've noticed that I can catch the exception if I let it propagate to the controller and catch it there, but it seems I can't implement exception handling in the service which is weird.
What am I missing?
I got to the bottom of this and I'm posting it in case anyone else runs into this. The issue occurred because the try/catch block was in a transactional service. Although grails reported that the exception was thrown during the save() call, in reality it was called AFTER the entire method, when the transaction was committed.
So it seems that:
flush: true has no effect on transactional services
It's not possible to catch GORM related exceptions in transactional services, at least not without some work
I finally worked around this by manually managing the transaction myself i.e.
try {
User.withNewTransaction {
User user = User.get(id); // Must reload object
.. // do stuff
user.save(flush:true)
}
} catch (OptimisticLockingException ex) {
...
}
I hope this is of use to someone else!
I spent some time working on this problem and have written a more complete solution to handle the case of an optimistic locking exception in Grails.
Firstly, though the exception reported in the stack trace is StaleObjectStateException, the actual exception that gets thrown is HibernateOptimisticLockingFailureException (not "OptimisticLockingException"). Secondly, if you want to generalize this to handle arbitrary closures which modify domain objects, you need to rethrow exceptions thrown inside the closure.
The following static function will take an object and a closure that operates on the object, save it, and if it fails, retry again until it succeeds:
public static retryUpdate(Object o, Closure c) throws Exception {
def retVal
int retryCount = 0
while (retryCount < 5) {
try {
Model.withTransaction { status ->
retVal = c(status)
o.save()
}
return retVal
} catch (HibernateOptimisticLockingFailureException e) {
log.warn "Stale exception caught saving " + o
if (++retryCount >= 3) { // if retry has failed three times, pause before reloading
Thread.sleep(1000)
}
o.refresh()
} catch (UndeclaredThrowableException e2) {
// rethrow exceptions thrown inside transaction
throw e2.getCause()
}
}
return null
}
Model in this case is any GORM model class, doesn't matter which one. In particular it doesn't matter if it is the class of the passed-in object.
Example of use:
AnotherModelClass object = AnotherModelClass.get(id)
retryUpdate(object) {
object.setField("new value")
}