Dart Async Do Something Else Then Wait - dart

I've read several stackoverflow questions, dart documents, and even watched a video on async and await. I haven't found an answer to my question. I would like to call an async method, execute other code, and then wait on the completion of the async task.
Here's an example of what I'm working with. This is my component
Credit credit;
...
Future<Null> getCredit(id) async {
try {
credit = await _creditService.getCredit(id);
}
catch (e) {
errorMessage = e.toString();
}
}
...
void onUpdateCredit(int credit_id) {
getCredit(credit_id);
creditDialogTitle = 'Update Credit';
creditArtistIndex = credit.artist_id;
instrument = credit.instrument;
creditNotes = credit.notes;
creditDialog.open();
}
This code crashes because credit is null when an attempt is made to use it. One way around it is combine the two methods:
Future<Null> onUpdateCredit(id) async {
try {
credit = await _creditService.getCredit(id);
creditDialogTitle = 'Update Credit';
creditArtistIndex = credit.artist_id;
instrument = credit.instrument;
creditNotes = credit.notes;
creditDialog.open();
}
catch (e) {
errorMessage = e.toString();
}
}
Nothing is done is parallel and, if I need the credit some where else in my code, I would have to duplicate the try/catch portion of the method. I could also code it like this:
void onUpdateCredit(int credit_id) {
credit = null;
getCredit(credit_id);
creditDialogTitle = 'Update Credit';
while (credit == null) {//wait a period of time}
creditArtistIndex = credit.artist_id;
instrument = credit.instrument;
creditNotes = credit.notes;
creditDialog.open();
}
In other situations, I do something similar to this in my html with *ngIf="var != null" where var is populated by a future.
Is there a better way than using while (credit == null) ? This example only executes one instruction between the request and the completion so is trivial. I'm sure I'll other situations where I have a lot to do in between. I'm also adding the service method:
Future<Credit> getCredit(int id) async {
try {
String url = "http://catbox.loc/credits/${id.toString()}";
HttpRequest response = await HttpRequest.request(
url, requestHeaders: headers);
Map data = JSON.decode(response.responseText);
final credit = new Credit.fromJson(data);
return credit;
}
catch (e) {
throw _handleError(e);
}
}
Update
Based on #Douglas' answer, this works:
Future<Null> onUpdateCredit(id) async {
Future future = getCredit(id);
creditDialogTitle = 'Update Credit';
await future;
creditArtistIndex = credit.artist_id;
instrument = credit.instrument;
creditNotes = credit.notes;
creditDialog.open();
}
I then eliminated the intervening method.
Future<Null> onUpdateCredit(id) async {
try {
Future<Credit> future = _creditService.getCredit(id);
creditDialogTitle = 'Update Credit';
credit = await future;
creditArtistIndex = credit.artist_id;
instrument = credit.instrument;
creditNotes = credit.notes;
creditDialog.open();
}
catch (e) {
errorMessage = e.toString();
}
}

getCredit(credit_id) does not just kick off an asynchronous call, it also returns a Future object - immediately. Store that object in a local variable, and you can use it later to asynchronously execute additional code when it completes.
There are two ways to use that Future object. The easier and more fluent way requires that you declare onUpdateCredit to be async. Inside an async function, the line await futureObject will cause all code after that line to be executed asynchronously after the Future completes. The complete version of onUpdateCredit using this technique would look like this:
Future<Null> onUpdateCredit(int credit_id) async {
Future future = getCredit(credit_id);
creditDialogTitle = 'Update Credit';
await future;
creditArtistIndex = credit.artist_id;
instrument = credit.instrument;
creditNotes = credit.notes;
creditDialog.open();
}
The other way is to explicitly register the rest of your code as a callback using .then(). That would look like this:
void onUpdateCredit(int credit_id) {
Future future = getCredit(credit_id);
creditDialogTitle = 'Update Credit';
future.then((_) => {
creditArtistIndex = credit.artist_id;
instrument = credit.instrument;
creditNotes = credit.notes;
creditDialog.open();
});
}
Note that in either case, if the exception path occurs in getCredit(id) you will get errors for credit not being set. If you truly want the exception to be swallowed silently, you should have its handler fill in a default value for credit so that code that assumes it completed normally will still work.
Also note that your while loop version would fail - Dart, like JavaScript, is not truly multithreaded, and busy waiting like that will block the event loop forever, preventing the code that would set credit from ever running.
A short summary of how async and await work in general:
Future someFunc(args) async {
...
return value;
}
is equivalent to:
Future someFunc(args) {
return new Future(() => {
...
return value;
}
}
The code inside gets executed on a later iteration of the event loop, and the returned future completes either successfully with value or exceptionally with anything thrown in that code.
Meanwhile this:
try {
value = await someFutureObject;
...more code here...
} catch (e) {
...exception handling here...
}
is equivalent to:
someFutureObject.then((value) => {
...more code here...
}).catchError((e) => {
...exception handling here...
});
The most common use case is someVar = await someAsyncCall();, but you can save the Future itself by omitting await, or you can await on an arbitrary Future object no matter where it comes from.
However - and this is what makes await and async so incredibly convenient - you can have 5 different exit points from the async function interspersed with three await calls (in the same async function) in assorted places inside 13 nested loops, switches, try/catch, and if blocks, and Dart will automatically figure out the necessary tree of callbacks to make it all follow the same code path as if all the calls were synchronous.

Related

Error thrown from Future.wait() is not caught in a try-catch block

I am failing to understand, why the error thrown from addItem method in below code is not caught in the try-catch block
void main() async {
var executor = Executor();
var stream = Stream.fromIterable([0, 1, 2, 3, 4, 5, 6, 7]);
try {
await for (var _ in stream) {
executor.submit(() => demoMethod());
}
await executor.execute();
} catch (e) {
print(e);
}
}
Future<void> demoMethod() async {
var list = [1, 2, 3, 1, 4, 5];
var executor = Executor();
var test = Test();
for (var element in list) {
executor.submit(() => test.addItem(element));
}
await executor.execute();
test.list.forEach(print);
}
class Test {
var list = <int>[];
Future<void> addItem(int i) async {
if (list.contains(i)) {
throw Exception('Item exists');
}
list.add(i);
}
}
class Executor {
final List<Future<void>> _futures = [];
bool _disposed = false;
void submit(Future<void> Function() computation) {
if (!_disposed) {
_futures.add(computation());
} else {
throw Exception('Executor is already disposed');
}
}
Future<void> execute() async {
await Future.wait(_futures, eagerError: true);
_disposed = true;
}
}
but below code is able to catch the error properly
void main() async {
var executor = Executor();
try {
for (var i = 0; i < 10; i++) {
executor.submit(() => demoMethod());
}
await executor.execute();
} catch (e) {
print(e);
}
}
I am guessing it has something to do with the stream processing.
It's the stream.
In your other examples, you synchronously run through a loop a and call Executor.submit with all the computations, then immediately call executor.execute().
There is no asychronous gap between calling the function which returns a future, and Future.wait starting to wait for that future.
In the stream code, each stream events starts an asynchronous computation by calling Executor.submit. That creates a future, stores it in a list, and goes back to waiting for the stream.
If that future completes, with an error, before the stream ends and Future.wait gets called, then there is no error handler attached to the future yet. The error is then considered unhandled, and is reported to the current Zone's uncaught error handler. Here that's the root zone, which means it's a global uncaught error, which may crash your entire program.
You need to make sure the future doesn't consider its error unhandled.
The easiest way to do that is to change submit to:
void submit(Future<void> Function() computation) {
if (!_disposed) {
_futures.add(computation()..ignore());
} else {
throw StateError('Executor is already disposed');
}
}
The ..ignore() tells the future that it's OK to not have an error handler.
You know, because the code will later come back and call executor.execute, that any errors will still be reported, so it should be safe to just postpone them a little. That's what Future.ignore is for.
(Also changed Exception to StateError, because that's what you should use to report people using objects that have been disposed or otherwise decommissioned.)

Neo4j transaction with array of queries with js-neo4j-driver

I want to pass an n-number of cypher-queries to a neo4j-transaction and I am thinking about a good approach.
At the moment I have a working approach that takes the array-item or if it is not available a dummy-query. (Code below)
I believe this is not best-practice. Does anybody know or have an idea how this can be done better?
function Neo4jTransaction(QueryArray) {
const session = driverWrite.session();
const tx = session.beginTransaction();
tx.run(QueryArray[0] || "RETURN 0")
tx.run(QueryArray[1] || "RETURN 0")
tx.run(QueryArray[2] || "RETURN 0")
tx.run(QueryArray[3] || "RETURN 0")
.then(result => {
return tx.commit()
}).then(() => {
session.close()
driverWrite.close()
}).catch(exception => {
console.log(exception)
session.close()
driverWrite.close()
})
}
First, if you have an array, you might want to iterate over it. Second, tx.run() returns a Promise that you need to catch if it fails. In your code, it is called 4 times in a row, but only the last one waits for the result and catches the error. I looks like some lines of the code are missing.
neo4j-driver documentation gives a good example on explicit transactions: https://github.com/neo4j/neo4j-javascript-driver#explicit-transactions
The queries are executed sequentially. If one fails the whole transaction will be rolled back.
async function neo4jTransaction(queryArray) {
const session = driver.session();
const txc = session.beginTransaction();
try {
for (const query of queryArray) {
await txc.run(query || 'RETURN 0');
}
await txc.commit();
} catch (e) {
await txc.rollback();
return Promise.reject(e);
} finally {
await session.close();
}
}

can I know when a StreamSubscription is cancelled?

is there anything like onCancel for a StreamSubscription?
example:
var subscription = someStream.listen((item) => null);
subscription.cancel(); // does this trigger any event?
I ended up creating a _StreamSubscriptionDelegate that delegates all methods and so I can put some logic when the subscription is cancelled, however, maybe there is an easier solution to it.
If the stream comes from a StreamController, then the controller is notified of the cancel. The listener is expected to keep track of their own subscription, so if one part of the client code needs to know that another part has cancelled the stream, then wrapping the subscription in something which records that you cancelled it, is a perfectly good approach.
Another approach could be to wrap the stream before listening to it:
Stream<T> onCancel<T>(Stream<T> source, void onCancel()) async* {
bool isCancelled = true;
try {
await for (var event in source) {
yield event; // exits if cancelled.
}
isCancelled = false;
} finally {
if (isCancelled) onCancel();
}
}
or
Stream<T> onCancel<T>(Stream<T> source, void onCancel()) {
var sink = StreamController<T>();
sink.onListen = () {
var subscription = source.listen(sink.add, onError: sink.onError, onDone: sink.close);
sink
..onPause = subscription.pause
..onResume = subscription.resume
..onCancel = () {
subscription.cancel();
onCancel();
};
};
return sink.stream;
}

Chaining Futures Do Not Execute In Order

I'm currently reading variables from a Bluetooth device. This obviously takes an undetermined amount of time, so I am using futures (This method is readCharacteristic in my code down below).
More than one read operation cannot take place at a time - if a second read operation is started while a first operation is still in progress, Flutter will throw an error.
My understanding was that chaining futures together using .then() would only allow the next statement to execute when the previous call had finished. This idea seems to be true until I try to read a third value - that is when the error is thrown, because of the overlapping read events.
Here is my code:
readCharacteristic(scanDurationCharacteristic)
.then((list) => sensorScanDuration = list[0].toDouble())
.then((_) {
readCharacteristic(scanPeriodCharacteristic)
.then((list) => sensorScanPeriod = list[0].toDouble());
}).then((_) {
readCharacteristic(aggregateCharacteristic)
.then((list) => sensorAggregateCount = list[0].toDouble());
}).then((_) {
readCharacteristic(appEUICharacteristic)
.then((list) => appEUI = decimalToHexString(list));
}).then((_) {
readCharacteristic(devEUICharacteristic)
.then((list) => devEUI = decimalToHexString(list));
}).then((_) {
readCharacteristic(appKeyCharacteristic)
.then((list) => appKey = decimalToHexString(list));
});
What is a better way to ensure that these read events will not overlap?
Although R.C Howell answer is correct, prefer using async/await keywords instead. This is much more readable and you're less likely to make an error
Future<void> scanBluetooth() async {
sensorScanDuration = (await readCharacteristic(scanDurationCharacteristic))[0].toDouble();
sensorScanPeriod = (await readCharacteristic(scanPeriodCharacteristic))[0].toDouble();
sensorAggregateCount = (await readCharacteristic(aggregateCharacteristic))[0].toDouble();
appEUI = await readCharacteristic(appEUICharacteristic).then(decimalToHexString);
devEUI = await readCharacteristic(devEUICharacteristic).then(decimalToHexString);
appKey = await readCharacteristic(appKeyCharacteristic).then(decimalToHexString);
}
If you would like to chain Futures, you must return the previous Future from within the then method of the previous Future.
The documentation says to chain like so,
expensiveA()
.then((aValue) => expensiveB())
.then((bValue) => expensiveC())
.then((cValue) => doSomethingWith(cValue));
Which is the same as,
expensiveA()
.then((aValue) {
return expensiveB();
}).then((bValue) {
return expensiveC();
}).then((cValue) => doSomethingWith(cValue));
As this applies to your case,
readCharacteristic(scanDurationCharacteristic)
.then((list) {
sensorScanDuration = list[0].toDouble();
return readCharacteristic(scanPeriodCharacteristic);
}).then((list) {
sensorScanPeriod = list[0].toDouble());
return readCharacteristic(aggregateCharacteristic);
}).then((list) {
sensorAggregateCount = list[0].toDouble());
return readCharacteristic(appEUICharacteristic);
}).then((list) {
appEUI = decimalToHexString(list));
return readCharacteristic(devEUICharacteristic);
}).then((list) {
devEUI = decimalToHexString(list));
return readCharacteristic(appKeyCharacteristic);
}).then((list) => appKey = decimalToHexString(list));

Execute Futures until a parameter becomes true

I launch a request to a server with a future "requestServer".
I would like to poll a system for a specific value (passed from false to true, when request is done) and return when finished.
Code could be like that, but "while" synchronous and "checkOperation" is asynchronous?
return requestServer().then((operation) {
var done = false;
while (done)
return checkOperation(operation).then((result) {
done = (result == true);
});
sleep(10);
}
});
Any ideas ?
I guess this is not exactly what you want but as far as I know there is no way to block execution so you have to use callbacks.
void main(List<String> args) {
// polling
new Timer.periodic(new Duration(microseconds: 100), (t) {
if(isDone) {
t.cancel();
someCallback();
}
});
// set isDone to true sometimes in the future
new Future.delayed(new Duration(seconds: 10), () => isDone = true);
}
bool isDone = false;
void someCallback() {
print('isDone: $isDone');
// continue processing
}
You can of course pass the callback as parameter instead of hardcode it, because functions are first class members in Dart.
Polling doesn't work very well for async. It is better to wait for a signal from the thing that must complete.
Günter Zöchbauer's answer shows you how to poll anyway, by sampling with a timer.
As an alternative, it would be better to not have a boolean done, but instead complete another future when you are ready. This is busy-polling, which polls again as soon as a result comes back, which may be more intensive than you need. Using timer based polling can be more efficient if you don't need the result as soon as possible.
return requestServer().then((operation) {
var completer = new Completer();
void poll(result) {
if (!result) {
operation.then(poll, onError: completer.completeError);
} else {
completer.complete();
}
}
poll(false);
return completer.future;
});
(Code not really tested, since I don't have your requestServer).
When you want build functions that return Futures, it is sometimes useful to use Completers. Think that requestServer() is living in the Future too, so you will have threat the result as a Future.
return requestServer().then((operation) {
// This is necessary then you want to control async
// funcions.
Completer completer = new Completer();
//
new Timer.periodic(const Duration(seconds: 10), (_) {
checkOperation(operation).then((result) {
// Only when the result is true, you pass the signal
// that the operation has finished.
// You can alse use `completer.complete(result)` if you want
// to pass data inside of the future.
if (result == true) completer.complete();
});
});
// You return the future straight away.
// It will be returned by requestServer();
return completer.future;
});
I use a function like this in a TestUtil library:
static Future<bool> waitUntilTrue(bool Function() callback,
{Duration timeout: const Duration(seconds: 2),
Duration pollInterval: const Duration(milliseconds: 50)}) {
var completer = new Completer<bool>();
var started = DateTime.now();
poll() {
var now = DateTime.now();
if (now.difference(started) >= timeout) {
completer.completeError(Exception('timed out in waitUntilTrue'));
return;
}
if (callback()) {
completer.complete(true);
} else {
new Timer(Duration(milliseconds: 100), () {
poll();
});
}
}
poll();
return completer.future;
}
And then in my test code I'll do something like:
await TestUtil.waitUntilTrue(() => someObj.isDone);
Edit:
Note that if you're using this in a testWidgets test, you have to do a little extra, since it relies on real async work happening:
await tester.runAsync<bool>(
() => TestUtil.waitUntilTrue(() => myObj.isLoaded),
additionalTime: Duration(seconds: 5));

Resources