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);
});
}
}
Related
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;
}
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
I have a forEachAsync inside an async* Stream and can't yield.
Stream<ProjectState> _mapProjectSelectedEventToState(ProjectSelected event) async* {
try {
yield ProjectLoading(
message: 'Fetching database',
fetchedCount: 0,
totalCount: 1,
);
await forEachAsync(fileModels, (FileEntity fileModel) async {
await downloader.download(filename: fileModel.hashName);
_totalMediaFilesFetched++;
//// ERROR - THIS DOES NOT WORK ////
yield (ProjectLoadingTick(
_totalMediaFiles,
_totalMediaFilesFetched,
));
}, maxTasks: 5);
} catch (error, stacktrace) {
yield ProjectFailure(error: error);
}
}
I've tried other means by dispatching the message and converting it to a state but it doesn't work as well. It seems like the whole app is blocked by this await forEachAsync.
I'm using the bloc pattern which reacts to the emited ProjectStates based on the current ProjectSelected event
Your attempt doesn't work because you're using yield in a callback, not in the function that's returning a Stream. That is, you're attempting the equivalent of:
Stream<ProjectState> _mapProjectSelectedEventToState(ProjectSelected event) async* {
...
await forEachAsync(fileModels, helperFunction);
...
}
Future helperFunction(FileEntity fileModel) async {
...
yield ProjectLoadingTick(...);
}
which doesn't make sense.
Since care about forEachAsync's ability to set a maximum limit to the number of outstanding asynchronous operations,
you might be better off using a StreamController that you can manually add events to:
var controller = StreamController<ProjectState>();
// Note that this is not `await`ed.
forEachAsync(fileModels, (FileEntity fileModel) async {
await downloader.download(filename: fileModel.hashName);
_totalMediaFilesFetched++;
controller.add(ProjectLoadingTick(
_totalMediaFiles,
_totalMediaFilesFetched,
));
},
maxTasks: 5);
yield* controller.stream;
How we can merge multiple FutureTask so that we can get a callback for all at the same response.
We use Future<T> like
Future<String> getData(int duration) async {
await Future.delayed(Duration(seconds: duration)); //Mock delay
return "This a test data for duration $duration";
}
Call above method like getData(2).then((value) => print(value));
If we want to call multiple Future Task, then how can we do that?
To execute all futures concurrently, use Future.wait This takes a list of futures and returns a future of lists:
Suppose you have these futures.
class CovidAPI {
Future<int> getCases() => Future.value(1000);
Future<int> getRecovered() => Future.value(100);
Future<int> getDeaths() => Future.value(10);
}
You can get all the futures together using Future.wait([list of futures])
final api = CovidAPI();
final values = await Future.wait([
api.getCases(),
api.getRecovered(),
api.getDeaths(),
]);
print(values); // [1000, 100, 10]
This is ideal when the futures are independent, and they don't need to execute sequentially.
Source : https://codewithandrea.com/videos/top-dart-tips-and-tricks-for-flutter-devs/
For it, FutureGroup can be used to combine multiple streams
FutureGroup provides us the functionality of combining multiple
futures into one single group, which will give the callback at the end
when all future tasks work gets completed.
Dependency:
dependencies:
async: ^2.4.1
How to implement FutureGroup?
FutureGroup futureGroup = FutureGroup();
futureGroup.add(future1);
futureGroup.add(future2);
futureGroup.add(future3);
Use:
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.
Note: This will be called only once when all the Future Task gets completed, here it will run after 6 seconds.
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();
}
}