"where" on iterable with async test - asynchronous

Am I missing an integrated functionality of the dart language (or a lightweight library) to do what where() does on iterables, only with an async test? I'm currently using my own implementation:
Future<Iterable<T>> whereAsync<T>(
Iterable<T> collection, Future<bool> Function(T) asyncTest) async {
Map<T, bool> mappedCollection = Map();
await Future.wait(collection.map((element) async =>
mappedCollection[element] = await asyncTest(element)));
return mappedCollection.entries
.where((entry) => entry.value)
.map((entry) => entry.key);
}

I think you code example looks a more complicated than it needs to be. Also, you should try read something about Streams since they are the async version of iterator.
I have tried to make my own version of you code which I think solves the same problem:
import 'dart:async';
void main() {
final list = [1, 2, 3];
whereAsync(Stream.fromIterable(list), (e) => e != 2).forEach(print); // 1 3
}
Stream<T> whereAsync<T>(Stream<T> stream, FutureOr<bool> check(T input)) async* {
await for (var element in stream) {
if (await check(element)) {
yield element;
}
}
}

Since the code posed in the question indexes into a map using the elements of the Iterable, it will suffer from a potential defect when two or more elements are equal.
Here's a method which is simpler and doesn't exhibit the potential defect.
class Pair<T1, T2> {
final T1 first;
final T2 second;
Pair(this.first, this.second);
}
Future<Iterable<T>> whereAsync<T>(Iterable<T> items, Future<bool> Function(T) test) async {
final futures = items.map((e) => test(e).then((v) => Pair(e, v)));
final results = await Future.wait(futures);
return results.where((e) => e.second).map((e) => e.first);
}

Related

Can I use an asynchronous function in a stream.map()?

Is it possible to pass an asynchronous function into a stream.map() function?
final CollectionReference myCollection =
FirebaseFirestore.instance.collection('foo');
Future<Stream<MyModel>> get myStream async {
// incorrect code, but I want to do something like this
return await myCollection.snapshots().map(_mappingFunc);
}
Future<MyModel> _mappingFunc(QuerySnapshot snapshot) async {
// some async code
}
That's not possible, since the declaration of the map is the following:
Stream<S> map <S>(
S convert(
T event
)
)
map takes a convert function which is of type S, basically the type of the Stream used.
You can use the asyncMap() method:
https://api.dart.dev/stable/2.9.1/dart-async/Stream/asyncMap.html
You can either use asyncMap to perform the mapping which outputs to a Stream or you can wrap your function and await the result to use an asynchronous function with List.map to get a List result:
myCollection.snapshots().map((e) async => await _mappingFunc(e));
Another option is to use an async* method to achieve a Stream output:
Stream<MyModel> get myStream async* {
for (final snapshot in myCollection.snapshots()) {
yield await _mappingFunc(snapshot);
}
}

Run async code in synchronous way in Dart

Having the following code:
Future<String> checkPrinter() async {
await new Future.delayed(const Duration(seconds: 3));
return Future.value("Ok");
}
String getPrinterStatus() {
checkPrinter().then((value) {
return 'The printer answered: $value';
}).catchError((_) {
return "Printer does not respond!";
});
}
void main() {
print(getPrinterStatus());
}
The output is "null" because the function getPrinterStatus() returns without waiting for checkPrinter to complete (correctly i have a warning telling me that getPrinterStatus does not return a string).
What should i do to make getPrinterStatus to wait for checkPrinter()?
Future<String> getPrinterStatus() async {
await checkPrinter().then((value) {
return 'The printer answered: $value';
}).catchError((_){
return "Printer does not respond!";});
}
There are no such way to make async code to run in a synchronous way in Dart. So you need to make it so async methods returns a Future so it is possible to await the answer.
The problem you have in your code is your are using the then method but does not take into account that this method returns a Future you should return. So e.g.:
String getPrinterStatus() {
checkPrinter().then((value) {
return 'The printer answered: $value';
}).catchError((_){
return "Printer does not respond!";});
}
The two return statements in this example are used for the method your are giving as argument for then() and are therefore not used to return from getPrinterStatus().
Instead, then() returns a Future<String> which will complete with the value.
So you need to carry the async through your program. Your code could make use of some language features you gets from marking a method async so I have tried to rewrite your code so it works as expected and make use of this features:
Future<String> checkPrinter() async {
await Future.delayed(const Duration(seconds: 3));
return "Ok";
}
// Just an example that the method could be written like this.
// Future.delayed takes a computation as argument.
Future<String> checkPrinterShorter() =>
Future.delayed(const Duration(seconds: 3), () => 'Ok');
Future<String> getPrinterStatus() async {
try {
return 'The printer answered: ${await checkPrinter()}';
} catch (_) {
return "Printer does not respond!";
}
}
Future<void> main() async {
print(await getPrinterStatus());
}
There are some changes I think you should notice:
If a method is marked as async the returned value will automatically be packed into a Future<Type> so you don't need to return Future.value("Ok") but can just return Ok.
If a method is marked as async you can use await instead of using then(). Also, you can use normal exception handling so I have rewritten getPrinterStatus to do that.
Your getPrinterStatus() is not async. main() function will never wait for it's execution.
Make it async. Refactor your main() for something like
void main() {
getPrinterStatus().then((value) {
print(value);
}).catchError((_){
return "Printer does not respond!";});
}

Flutter Return Length of Documents from Firebase

Im trying to return the length of a list of documents with this function:
Future totalLikes(postID) async {
var respectsQuery = Firestore.instance
.collection('respects')
.where('postID', isEqualTo: postID);
respectsQuery.getDocuments().then((data) {
var totalEquals = data.documents.length;
return totalEquals;
});
}
I'm initialize this in the void init state (with another function call:
void initState() {
totalLikes(postID).then((result) {
setState(() {
_totalRespects = result;
});
});
}
However, when this runs, it initially returns a null value since it doesn't have time to to fully complete. I have tried to out an "await" before the Firestore call within the Future function but get the compile error of "Await only futures."
Can anyone help me understand how I can wait for this function to fully return a non-null value before setting the state of "_totalRespsects"?
Thanks!
I think you're looking for this:
Future totalLikes(postID) async {
var respectsQuery = Firestore.instance
.collection('respects')
.where('postID', isEqualTo: postID);
var querySnapshot = await respectsQuery.getDocuments();
var totalEquals = querySnapshot.documents.length;
return totalEquals;
}
Note that this loads all documents, just to determine the number of documents, which is incredibly wasteful (especially as you get more documents). Consider keeping a document where you maintain the count as a field, so that you only have to read a single document to get the count. See aggregation queries and distributed counters in the Firestore documentation.
Perfect code for your problem:
int? total;
getLength() async {
var getDocuments = await DatabaseHelper.registerUserCollection
.where("register", isEqualTo: "yes")
.get();
setState(() {
total = getDocuments.docs.length;
});
}
#override
void initState() {
super.initState();
getLength();
if (kDebugMode) {
print(total);
}
}

Firebase Database Snapshot ForEach Async Await

async/await doesn't seem to work with firebase forEach. This code runs console.log for the first child of the snapshot and then just hangs. Is this a bug or am I missing something?
main()
async function main() {
const snap = await root.ref('somepath')
.once('value')
snap.forEach(async val => {
await console.log(val.key)
})
}
It's an unintentional consequence of a feature, sometimes knows as a bug. From the firebase documentation:
an async function always returns a Promise. And a promise is true-y value:
!!Promise.resolve===true.
As a workaround, try this solution:
main()
async function main() {
const snap = await root.ref('somepath')
.once('value')
snap.forEach(function wrapper(){async val => {
await console.log(val.key)
}})
It won't wait for the promise to complete before moving to the next snapshot though. That would require a bit more code, but that may not be what you need anyways.
I use something like this:
import * as admin from "firebase-admin";
type FunctionOnDataSnapshot = (childSnapshot: admin.database.DataSnapshot) => Promise<any>;
export const asyncForEach = async (dataSnapshot: admin.database.DataSnapshot, childFunction: FunctionOnDataSnapshot) => {
const toWait = [];
dataSnapshot.forEach(childSnapshot => {
toWait.push(childFunction((childSnapshot)));
});
await Promise.all(toWait);
};

How do I use await inside a generator?

I'm having a generator that, among other operations, queries a database, like
function* current(db) {
const items = await db.collection('...').find({ ... });
for (const item of items)
if (...) yield item;
}
which isn't valid syntax. Using promises and yielding from a then, isn't possible either.
What should I do then? How can I use asynchronous operation inside a generator?
Calling await in generators (aka async generators) has been supported natively in Node v10+ (released April 2018) and Chrome since v63, as well as Firefox v57.
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function getAsyncData() {
await sleep(1000); // simulate database/network delay...
return [1, 2, 3, 4, 5]; // ...then return some data
}
// async generator
async function* filterAsyncData() {
const items = await getAsyncData(); // await call in generator
for (const item of items)
if (item % 2) yield item;
}
(async function main() {
// for-await syntax
for await (const filteredItem of filterAsyncData())
console.log(filteredItem);
// classic iterator syntax
const fadIt = filterAsyncData();
while (true) {
const result = await fadIt.next(); // note await
if (result.done) break;
console.log(result.value);
}
}());
Compared to a regular (non-async) generators, remember to use await before calling .next(). Alternatively, the new for-await-of syntax has been available since Node v9.2, and can be used in Node v10 without any flags.
For older versions of node (or for Jest), you can use the Babel plugin transform-async-generator-functions.
You can't combine async with a generator function. That said you most likely don't want to be using a generator in your case and instead just have an async function:
async function current(db) {
while (true) {
const latest = await db.collection('messages').findOne({}, {
sort: {
timestamp: -1
}
});
return smth;
}
}
The generator will be generated for you when the async is transpiled.

Resources