Waiting for Futures raised by other Futures - dart

I'm using the Lawndart library to access browser data, and want to collect the results of a set of queries. Here's what I thought should work:
numberOfRecordsPerSection(callback) {
var map = new Map();
db_sections.keys().forEach((_key) {
db_sections.getByKey(_key).then((Map _section) {
int count = _section.length;
map[_key] = count;
});
}).then(callback(map));
}
However, when the callback is called, map is still empty (it gets populated correctly, but later, after all the Futures have completed). I assume the problem is that the Futures created by the getByKey() calls are not "captured by" the Futures created by the forEach() calls.
How can I correct my code to capture the result correctly?

the code from How do I do this jquery pattern in dart? looks very similar to yours
For each entry of _db.keys() a future is added to an array and then waited for all of them being finished by Future.wait()
Not sure if this code works (see comments on the answer on the linked question)
void fnA() {
fnB().then((_) {
// Here, all keys should have been loaded
});
}
Future fnB() {
return _db.open().then((_) {
List<Future> futures = [];
return _db.keys().forEach((String key_name) {
futures.add(_db.getByKey(key_name).then((String data) {
// do something with data
return data;
}));
}).then((_) => Future.wait(futures));
});
}

Related

Dart/Flutter - "yield" inside a callback function

I need to yield a list for a function; however, I want to yield the list from within a callback function, which itself is inside the main function - this results in the yield statement not executing for the main function, but rather for the callback function.
My problem is very similar to the problem that was solved here: Dart Component: How to return result of asynchronous callback? but I cannot use a Completer because I need to yield and not return.
The code below should describe the problem better:
Stream<List<EventModel>> fetchEvents() async* { //function [1]
Firestore.instance
.collection('events')
.getDocuments()
.asStream()
.listen((snapshot) async* { //function [2]
List<EventModel> list = List();
snapshot.documents.forEach((document) {
list.add(EventModel.fromJson(document.data));
});
yield list; //This is where my problem lies - I need to yield for function [1] not [2]
});
}
Instead of .listen which handles events inside another function you can use await for to handle events inside the outer function.
Separately - you might want to reconsider the pattern when you yield List instances that are still getting populated inside an inner stream callback...
Stream<List<EventModel>> fetchEvents() async* {
final snapshots =
Firestore.instance.collection('events').getDocuments().asStream();
await for (final snapshot in snapshots) {
// The `await .toList()` ensures the full list is ready
// before yielding on the Stream
final events = await snapshot.documents
.map((document) => EventModel.fromJson(document.data))
.toList();
yield events;
}
}
I would like to add a suggestion for improvement here. The suggested await for solution should be avoided in some cases as it is non dismissible listener and it newer stops listening so this might lead to memory leaks. You could as well use .map to transform the stream yield results like so (havent tried to compile it, but main idea should be clear):
Stream<List<EventModel>> fetchEvents() { // remove the async*
Firestore.instance
.collection('events')
.getDocuments()
.asStream()
.map((snapshot) { // use map instead of listen
List<EventModel> list = List();
snapshot.documents.forEach((document) {
list.add(EventModel.fromJson(document.data));
});
return list; // use return instead of yield
});
}

How to know if a certain future is complete by avoiding a chain of future as return types?

Scenario
If I want to read from a file and store the data in a Map, and if that map is being used multiple times for validation.
Is it possible for me to do this without having to change the return type of all methods, that use the above mentioned map, to Future?
Example:
Map metadata = null
Future readFromFile async {
.... metadata = await File.readingfromFile(...);
}
Future getRegion(..) async {
if(metadata == null) { await readFromFile() }
return metadata["region"]
}
Using the above code if a method(like isValidRegion,etc) that uses and needs getRegion(..) to complete, then the return type of isValidRegion should be converted to Future.
Future<bool> isValidRegion(..) async {
return ((await getRegionData(...)) != null )
}
If that isValidRegion is present within another methods, then the return type of them have to be changed to Future as well.
Future<String> parse(...) async {
....
if(await isValidRegion()) {
...
}
...
}
What is an elegant way to avoid this chain of futures as return types?
Async execution is contagious, there is nothing you can do to get back from async to sync execution.
What you can do is to do the read from the file synchronous to avoid the problem in the first place (if this is possible, if you read it from a network connection, this might not be possible).

pass an errormessage from server to client

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.

How to store result of a Future for later use

Suppose our goal is to build a List which we will call storedResult.
We have a Future<List> returning function called multiple times:
Future<List> getList()...
Suppose that we will call our getList() function for the unknown multiple times and want to store the result of each List into our storedResult:
List storedResult = [];
someOtherList.forEach((element)){
getList().then((resultingList)){
storedResult.addAll(resultingList);
}
}
print(storedResult);
If we run this code, it will print as []. How do we express this so that the result is rather:
[resultingList, resultingList, ..., ]
Remember that Futures return immediately, but their associated callbacks run asynchronously. Specifically, the work that getList() does (and everything that then does) do not happen until execution returns to the event loop.
Your problem is that you're trying to print the result of an asynchronous operation synchronously, and that won't work.
If you want to use the stored result, then what you should do is something like:
Future<List<List>> globFutures(List someOtherList) {
List<Future> futures = [];
someOtherList.forEach((element) {
futures.add(getList());
});
// Automatically completes to a List containing all the
// results of all of the futures.
return Future.wait(futures);
}
main() {
var storedResults = [];
globFutures(someOtherList)
.then((List<List> results) {
storedResults = results;
// Run code in here that depends on storedResults being available.
print(storedResults);
});
// Do **NOT** use storedResults here, as it will be [].
// The Future functions have not yet run!
assert(storedResults == []);
};

indexed_db getObject() - how to return result

I would like to know how to define the data type and how to return the object (record) using getObject(). Currently, the only way that I have been able to use the result (record) outside of the function that obtains it is to call another function with the result. That way, the data-type does not need to be specified. However if I want to return the value, I need to define the data-type and I can't find what it is. I tried "dynamic" but that didn't appear to work. For example ":
fDbSelectOneClient(String sKey, Function fSuccess, String sErmes) {
try {
idb.Transaction oDbTxn = ogDb1.transaction(sgTblClient, 'readwrite');
idb.ObjectStore oDbTable = oDbTxn.objectStore(sgTblClient);
idb.Request oDbReqGet = oDbTable.getObject(sKey);
oDbReqGet.onSuccess.listen((val){
if (oDbReqGet.result == null) {
window.alert("Record $sKey was not found - $sErmes");
} else {
///////return oDbReqGet.result; /// THIS IS WHAT i WANT TO DO
fSuccess(oDbReqGet.result); /// THIS IS WHAT i'm HAVING TO DO
}});
oDbReqGet.onError.first.then((e){window.alert(
"Error reading single Client. Key = $sKey. Error = ${e}");});
} catch (oError) {
window.alert("Error attempting to read record for Client $sKey.
Error = ${oError}");
}
}
fAfterAddOrUpdateClient(oDbRec) {
/// this is one of the functions used as "fSuccess above
As someone else once said (can't remember who), once you start using an async API, everything needs to be async.
A typical "Dart" pattern to do this would be to use a Future + Completer pair (although there's nothing inherently wrong with what you've done in your question above - it's more a question of style...).
Conceptually, the fDbSelectOneClient function creates a completer object, and the function returns the completer.future. Then, when the async call completes, you call completer.complete, passing the value in.
A user of the function would call fDbSelectOneClient(...).then((result) => print(result)); to make use of the result in an async way
Your code above could be refactored as follows:
import 'dart:async'; // required for Completer
Future fDbSelectOneClient(String sKey) {
var completer = new Completer();
try {
idb.Transaction oDbTxn = ogDb1.transaction(sgTblClient, 'readwrite');
idb.ObjectStore oDbTable = oDbTxn.objectStore(sgTblClient);
idb.Request oDbReqGet = oDbTable.getObject(sKey);
oDbReqGet.onSuccess.listen((val) => completer.complete(oDbReqGet.result));
oDbReqGet.onError.first.then((err) => completer.completeError(err));
}
catch (oError) {
completer.completeError(oError);
}
return completer.future; // return the future
}
// calling code elsewhere
foo() {
var key = "Mr Blue";
fDbSelectOneClient(key)
.then((result) {
// do something with result (note, may be null)
})
..catchError((err) { // note method chaining ..
// do something with error
};
}
This future/completer pair only works for one shot (ie, if the onSuccess.listen is called multiple times, then the second time you will get a "Future already completed" error. (I've made an assumption on the basis of the function name fDbSelectOneClient that you are only expecting to select a single record.
To return a value from a single future multiple times, you'll probably have to use the new streams feature of the Future - see here for more details: http://news.dartlang.org/2012/11/introducing-new-streams-api.html
Note also, that Futures and Completers support generics, so you can strongly type the return type as follows:
// strongly typed future
Future<SomeReturnType> fDbSelectOneClient(String sKey) {
var completer = new Completer<SomeReturnType>();
completer.complete(new SomeReturnType());
}
foo() {
// strongly typed result
fDbSelectOneClient("Mr Blue").then((SomeReturnType result) => print(result));
}

Resources