How can I run a map function inside a Future method?
This is my code:
Future deactivateLogs(List<String> members) async {
return members.map((member) async {
return await logDataCollection
.document('log-$member')
.updateData({
'active': false,
});
});
}
When I call this method, the map function is not being executed. How can I change the syntax so that it runs?
The goal is that the data is being updated in the database (backend) for each of the individual 'member' strings.
The problem can also be made easier:
Future deactivateTribeLogs() {
List<int> list = [1, 2, 3];
list.map((e) => print(e));}
The map function does not run in this case either.
Many thanks in advance!
You have two problems:
You're using an asynchronous callback with Iterable.map, but nothing will wait for the callbacks to complete.
Iterable.map is lazy; if you never attempt to use the result of a callback, it won't bother executing that callback at all. (Iterable.map normally is meant for functional-style programming where the callbacks are "pure"; you usually don't want to use it with callbacks that have side-effects.)
When you call Iterable.map with an asynchronous callback, it returns an iterable of Futures. If you don't care about execution order of callbacks, you could use Future.wait on that directly:
Future<void> deactivateLogs(List<String> members) async {
var waitList = members.map((member) async {
return await logDataCollection.document('log-$member').updateData({
'active': false,
});
});
await Future.wait(waitList);
}
Doing so also will evaluate all elements of the returned list of Futures and will avoid your issue with Iterable.map's lazy evaluation.
You can also use for loop for that :
Future deactivateLogs(List<String> members) async {
for(var i = 0; i< members.length; i++) {
await logDataCollection
.document('log-' + members[i])
.updateData({
'active': false,
});
}
return ;
}
If you look at the definition of List::map, you will see that it returns an Iterable, which is not evaluated.
Another way of saying it is that List::map is lazy and will not run until the iterable is either explicitly iterated or converted to a list.
Actually from the documentation, it says:
Returns a new lazy Iterable with elements that are created by calling f on each element of this Iterable in iteration order.
To fix your problem, just append .toList() at the end of the .map call, and it will force the iterable to be evaluated.
Since you are not looking to return anything from the function, you can just use Future::forEach:
Future<void> deactivateLogs(List<String> members) =>
Future.forEach(members, (member) => logDataCollection
.document('log-$member')
.updateData({
'active': false,
});
);
Full example
void main() async {
print('start');
await deactivateLogs(["Foo", "Bar", "Hello", "World"]);
print('end');
}
Future<void> deactivateLogs(List<String> members) =>
Future.forEach(members, (member) {
final v = await Future(() => member);
print(v);
});
Output
start
Foo
Bar
Hello
World
end
Related
The dart documentation says that if the Future is completed, then the then methods will be run in one of the following microtasks. In the following example, everything works as expected because await is applied.
// print: exit_main, micro, then
main() async {
var p = Future.value(1);
await p;
scheduleMicrotask(() {
print('micro');
});
p.then((e) {
print('then');
});
print('exit_main');
}
But if await is removed, then the logic described above does not work. A microtask that is created before the then function is run after the then function is executed. But the then function is run after main, you can see that because a message is printed at the end of the main function.
// print: exit_main, then, micro
main() {
var p = Future.value(1);
scheduleMicrotask(() {
print('micro');
});
p.then((e) {
print('then');
});
print('exit_main');
}
In connection with what has been said, the following question. Why does the then function run before the created microtask?
Case 2 was expected to work just like the first case when await is applied.
Understood, no answer needed. Presumably, the Future.value function with a simple value completes the Future in the future microtask, i.e. the Future is scheduled to complete before another microtask is called. After the Future completes, all then methods are called immediately, provided that they return a non-terminated Future. The behavior changes if the future has already completed. An example is presented below.
main() {
var p = Future.value(1);
Timer.run(() {
scheduleMicrotask(() {
print('micro');
});
p.then((e) {
print('then');
});
print('exit_main');
});
}
Given that I have the following data:
var uniqueIds = [1, 2, 3, 4];
I want to perform async requests in order based on the input above:
List<Future<void>> futures = uniqueIds.map((e) {
return APIService.shared
.networkingRequest(input: e)
.then((value) => update())
.onError((error, stackTrace) => {
handleError(error)});
}).toList();
Where I want to trigger something like futures.waitInOrder(...) with whenComplete handler.
Because Dart begins running a Future's computation immediately upon the creation of that Future, the only practical way to do this is to create each Future immediately prior to awaiting their result, rather than mapping all the values to Futures up-front.
What that will look like is something like this:
void foo(List<int> uniqueIds) async {
for (final uniqueId in uniqueIds) {
await APIService.shared
.networkingRequest(input: uniqueId)
.then((value) => update())
.onError((error, stackTrace) {
handleError(error);
});
}
}
I know how Future.wait works
await Future.wait([
functionA(),
functionB()
]).then((data) {
});
but what I want to know is
What if the number of functions is not fixed and the number of functions to be called varies depending on the situation? (the functions are all the same.)
Elaborating on my comments, you can dynamically build a List of Futures and use Future.wait on that. For example:
Future<void> doAsynchronousStuff() async {
var waitList = <Future<void>>[];
if (someCondition) {
waitList.add(someAsynchronousFunction(someArgument));
}
if (someOtherCondition) {
waitList.add(someOtherAsynchronousFunction(someOtherArgument));
}
// ... and so on...
await Future.wait(waitList);
}
If you need to handle different return values from your asynchronous functions, you can use anonymous functions that set local variables. See Dart Future.wait for multiple futures and get back results of different types.
As long as you have an iterable reference, you can map that and return the Future function.
Future<void> main() async {
final List<int> resultFromApi = <int>[1, 2, 3, 4, 5];
final List<int> finalResult = await Future.wait(resultFromApi.map(
(int data) => complexProcess(data), // return the Future
));
print(finalResult);
}
Future<int> complexProcess(int data) async {
await Future<void>.delayed(const Duration(seconds: 1));
return data * 10;
}
I have found answers how to wait for 2 different futures when building widgets, but these were of the same type. Example from here:
But is there also a possibility if firstFuture() and secondFuture() return different value types - eg int and String (or different classes as in my use case)? The bool in AsyncSnapshot<List<bool>> snapshot is a challenge to me...
FutureBuilder(
future: Future.wait([
firstFuture(), // Future<bool> firstFuture() async {...}
secondFuture(),// Future<bool> secondFuture() async {...}
//... More futures
]),
builder: (
context,
// List of booleans(results of all futures above)
AsyncSnapshot<List<bool>> snapshot,
){
// Check hasData once for all futures.
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
// Access first Future's data:
// snapshot.data[0]
// Access second Future's data:
// snapshot.data[1]
return Container();
}
);
I also found another answer with different types, but this apples to functions not classes
List<Foo> foos;
List<Bar> bars;
List<FooBars> foobars;
await Future.wait<void>([
downloader.getFoos().then((result) => foos = result),
downloader.getBars().then((result) => bars = result),
downloader.getFooBars().then((result) => foobars = result),
]);
processData(foos, bars, foobars);
Well, you can always use Object as the lowest common return value of those two methods:
Future.wait<Object>([firstFuture(), secondFuture()])
But you will have to cast the entries of the resulting List<Object> back to what you think they should be. Not perfect, but it would work.
Personally, I like the method you already discovered a bit more, return only the bare minimum future, a Future<void> and have it write to the respective variables inside the method. That is not perfect either, but it keeps the type safety.
Below answer might help you
Use FutureGroup inside the package
dependencies: async: ^2.4.1
Code:
void main() {
Future<String> future1 = getData(2);
Future<String> future2 = getData(4);
Future<String> future3 = getData(6);
FutureGroup futureGroup = FutureGroup();
futureGroup.add(future1);
futureGroup.add(future2);
futureGroup.add(future3);
futureGroup.close();
futureGroup.future.then((value) => {print(value)});
}
Future<String> getData(int duration) async {
await Future.delayed(Duration(seconds: duration)); //Mock delay
return "This a test data";
}
Output:
I/flutter ( 5866): [This a test data, This a test data, This a test data] // Called after 6 seconds.
For more reference:
https://medium.com/flutterworld/flutter-futuregroup-d79b0414eaa7
See this code:
class SomeClass{
String someVariable;
SomeClass();
Future<String> getData () async {
Response response = await get('http://somewebsite.com/api/content');
Map map = jsonDecode(response.body); // do not worry about statuscode, trying to keep it minimal
someVariable = map['firstName'];
return 'This is the first name : $someVariable';
}
}
Now look at main:
void main(){
String someFunction() async {
SomeClass instance = SomeClass(); // creating object
String firstNameDeclaration = await instance.getData().then((value) => value);
return firstNameDeclaration;
}
}
When working with Future, like in the case of firstNameDeclaration why do I have to use .then() method to access the string object, since I am waiting for the function to finish?
When searching on the web, some people use .then() others don't, I am confused.
Kindly help me have a clearer understanding of how Futures and async functions overall work.
Background
Asynchronous operations let your program complete work while waiting for another operation to finish. Here are some common asynchronous operations:
Fetching data over a network.
Writing to a database.
Reading data from a file.
To perform asynchronous operations in Dart, you can use the Future class and the async and await keywords.
When an async function invokes "await", it is converted into a Future, and placed into the execution queue. When the awaited future is complete, the calling function is marked as ready for execution and it will be resumed at some later point. The important difference is that no Threads need to be paused in this model.
Futures vs async-await
When an async function invokes "await", it is converted into a Future, and placed into the execution queue. When the awaited future is complete, the calling function is marked as ready for execution and it will be resumed at some later point. The important difference is that no Threads need to be paused in this model.
async-await is just a a declarative way to define asynchronous functions and use their results into Future and it provides syntactic sugar that help you write clean code involving Futures.
Consider this dart code snipped involving Futures -
Future<String> getData(int number) {
return Future.delayed(Duration(seconds: 1), () {
return 'this is a future string $number.';
});
}
main(){
getData(10).then((data) => {
print(data)
});
}
As you can see when you use Futures, you can use then callback when the function return a future value. This is easy to manage if there is single "then" callback but the situation escalates quickly as soon as there are many nested "then" callbacks for example -
Future<String> getProductCostForUser() {
return getUser().then((user) => {
var uid = user.id;
return getOrder(uid).then((order) => {
var pid = order.productId;
return getProduct(pid).then((product) => {
return product.totalCost;
});
});
});
}
main(){
getProductCostForUser().then((cost) => {
print(cost);
});
}
As you can when there multiple chained "then" callback the code become very hard to read and manage. This problem is solved by "async-await". Above chained "then" callbacks can be simplified by using "async-await" like so -
Future<String> getProductCostForUser() async {
var user = await getUser();
var order = await getOrder(user.uid);
var product = await getProduct(order.productId);
return product.totalCost;
}
main() async {
var cost = await getProductCostForUser();
print(cost);
}
As you can above code is much more readable and easy to understand when there are chained "then" callbacks.
I hope this explains some basic concepts and understanding regarding the "async-await" and Futures.
You can further read about topic and examples here
Basically, you should either use await OR then(). However, Dart guidelines advocates that you should prefer use await over then() :
This code :
Future<int> countActivePlayers(String teamName) {
return downloadTeam(teamName).then((team) {
if (team == null) return Future.value(0);
return team.roster.then((players) {
return players.where((player) => player.isActive).length;
});
}).catchError((e) {
log.error(e);
return 0;
});
}
should be replaced by :
Future<int> countActivePlayers(String teamName) async {
try {
var team = await downloadTeam(teamName);
if (team == null) return 0;
var players = await team.roster;
return players.where((player) => player.isActive).length;
} catch (e) {
log.error(e);
return 0;
}
}
In your case, you should write :
void main(){
Future<String> someFunction() async {
SomeClass instance = SomeClass(); // creating object
String firstNameDeclaration = await instance.getData();
return firstNameDeclaration;
// Or directly : return await instance.getData();
// Or : return instance.getData();
}
}