Dart await Future.wait() with vs without inner async await - dart

In Dart, given an async method
Future transformObject(T object);
both of the following are legal:
Future transformData(Iterable<T> objects) async {
await Future.wait(objects.map((o) => transform(o));
}
and
Future transformData(Iterable<T> objects) async {
await Future.wait(objects.map((o) async { await transform(o); });
}
Is there any difference betwen the two, what is it and which one should I go for if I just want to transform all data objects asynchronously?

If transsform returns a Future, then there is no meaningful difference between transform, (o) => transform(o) and (o) async => await transform(o);. The more wrappings you add, the more overhead, but it's basically the same.
Your function, (o) async { await transform(o); } doesn't return the result of the future that transform returns, but if you don't need that value, then it shouldn't matter. (The name does suggest the you care about the value, though.) You'll still wait for that future to complete before being done.
What you could (possibly should) go for:
Future<List> transformData(Iterable<T> objects) =>
Future.wait(objects.map(transform));

Related

How to rewrite code with then to await version

I wrote code
pendingFutures.addAll({sqlQuery.hashCode: connection.query(sqlQuery, timeoutInSeconds: 3600)});
var queryResult = await pendingFutures[sqlQuery.hashCode];
var queryObj = RequestCacheStruct(sqlQuery.hashCode, DateTime.now(), queryResult! );
await pendingFutures.remove(sqlQuery.hashCode);
requestsCacheList.add(queryObj);
But it's seems that code above have different behavior than code below:
pendingFutures[sqlQuery.hashCode] =
connection.query(...)
.then((queryResult) {
// TODO: add queryResult into cache.
});
await pendingFutures[sqlQuery.hashCode];
How to fix first code to make it work as second one?
My friend who wrote second code said:
"In the first case, await pendingFutures[...] will return as soon as the request is finished.
In the second - when the request is finished and the record is added to the cache."
I'll try to distill your examples down so that they're proper apples-to-apples comparisons. Given:
theFuture = someAsyncOperation();
var result = await theFuture;
addToCache(result);
additionalWork();
and
theFuture = someAsyncOperation().then((result) => addToCache(result));
await theFuture;
additionalWork();
where theFuture is some non-local variable, then those await theFuture; lines should not result in any material difference: addToCache will be called before additionalWork.
However, a more interesting question (and probably what your friend meant) is that if theFuture is separately awaited:
await theFuture;
yetMoreWork();
will it be guaranteed that yetMoreWork is called after addToCache? In other words: if two .then() callbacks are registered on the same Future, are the callbacks guaranteed to be fired in order of registration? (From what I've observed, it seems that way, but I don't know if it's necessarily true, or even if it is, if it will be guaranteed to be true in the future.)
Back to your original question: how can you rewrite the first form to use only await but still guarantee that operations are serialized in a well-defined manner? You have to create a Future that completes after addToCache is called, and you can do that with a helper function:
Future<void> helperFunction() async {
var result = await someAsyncOperation();
addToCache(result);
}
theFuture = await helperFunction();
additionalWork();

How does Dart treat cascading Futures?

Say I have this class:
void main() async {
final example = ExampleClass();
await example.waitOne();
await example.waitOne();
print('finished');
}
class ExampleClass {
Future<void> waitOne() async {
await Future.delayed(Duration(seconds: 1));
print('1 second');
}
}
This code works exactly as I expect it to. It's output is as follows:
1 second
1 second
finished
Then we have this code:
void main() async {
final example = ExampleClass();
await example
..waitOne()
..waitOne();
print('finished');
}
This code now has cascading operators (..) and the output seems strange:
finished
1 second
1 second
The code skips the two futures and prints "finished" to the console first, then "1 second" gets printed twice at the same time (like Future#wait would do).
Why does Dart act in this way?
In your example with the cascading operator adding await doesn't do anything since the cascade operation doesn't return anything hence there is no future to be awaited and then finished is printed right away
Remember that the result of the cascade operator is the original object that you used it on. That is, for var result = object..x()..y()..z(), result will be assigned the value of object, regardless of what x, y, or z return. The values returned by x(), y(), and z() are ignored. It's the equivalent of:
object.x();
object.y();
object.z();
var result = object;
Your case, which involves Futures, is no different:
final example = ExampleClass();
await example
..waitOne()
..waitOne();
So you're doing the equivalent of:
final example = ExampleClass();
example.waitOne(); // The returned Future is ignored.
example.waitOne(); // The returned Future is ignored.
await example; // Incorrectly using await on a non-Future.
(Note that enabling the unawaited_futures and await_only_futures lints would catch this mistake.)
To properly wait, you can't use the cascade operator and will need to explicitly await the individual operations. Also see the issue Prefix await is cumbersome to work with. which discusses possible changes to the language to support using await with member or cascade operators.

Sequential processing of a variable number of async functions in Dart

I need to repeatedly call an asynchronous function in Dart, let's call it expensiveFunction, for a variable number of arguments. However, since each call is quite memory consuming, I cannot afford to run them in parallel. How do I force them to run in sequence?
I have tried this:
argList.forEach( await (int arg) async {
Completer c = new Completer();
expensiveFunction(arg).then( (result) {
// do something with the result
c.complete();
});
return c.future;
});
but it hasn't had the intended effect. The expensiveFunction is still being called in parallel for each arg in argList. What I actually need is to wait in the forEach loop until the expensiveFunction completes and only then to proceed with the next element in the argList. How can I achieve that?
You'll want to use a classic for loop here:
doThings() async {
for (var arg in argList) {
await expensiveFunction(arg).then((result) => ...);
}
}
There are some nice examples on the language tour.

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).

Making a Future block until it's done

Is it possible to block a function call that returns a future?
I was under the impression calling .then() does it, but that's not what I'm seeing in my output.
print("1");
HttpRequest.getString(url).then((json) {
print("2");
});
print("3");
What I'm seeing in my output is:
1
3
2
The getString method doesn't have an async that would allow me to await it and then executes asynchronously in any case.
static Future<String> getString(String url,
{bool withCredentials, void onProgress(ProgressEvent e)}) {
return request(url, withCredentials: withCredentials,
onProgress: onProgress).then((HttpRequest xhr) => xhr.responseText);
}
How do I make it blocking without placing an infinite while loop before step 3 waiting for step 2 to be completed (not that it would work anyways due to the single thread nature of Dart)?
The above HttpRequest loads a config.json file that determines how everything works in the app, if the request for a field in the config is done before the config.json file is done loading, it causes errors, so I need to wait until the file is done loading before I allow calling getters on the fields of the class or getters needs to wait for the once-off loading of the config.json file.
Update, this is what I eventually did to make it work after Günter suggested I use a Completer:
#Injectable()
class ConfigService {
Completer _api = new Completer();
Completer _version = new Completer();
ConfigService() {
String jsonURI =
"json/config-" + Uri.base.host.replaceAll("\.", "-") + ".json";
HttpRequest.getString(jsonURI).then((json) {
var config = JSON.decode(json);
this._api.complete(config["api"]);
this._version.complete(config["version"]);
});
}
Future<String> get api {
return this._api.future;
}
Future<String> get version {
return this._version.future;
}
}
And where I use the ConfigService:
#override
ngAfterContentInit() async {
var api = await config.api;
var version = await config.version;
print(api);
print(version);
}
Now I get blocking-like functionality without it actually blocking.
There is no way to block execution until asynchronous code completes. What you can do is to chain successive code so that it is not executed before the async code is completed.
One way to chain is then
print("1");
HttpRequest.getString(url) // async call that returns a `Future`
.then((json) { // uses the `Future` to chain `(json) { print("2"); }`
print("2");
});
print("3"); // not chained and therefore executed before the `Future` of `getString()` completes.
An async call is just scheduling code for later execution. It will be added to the event queue and when the tasks before it are processed it itself will be executed. After an async call is scheduled the sync code `print("3") is continued.
In your case HttpRequest.getString() schedules a call to your server and registers (json) { print("2") as callback to be called when the response from the server arrives. Further execution of the application doesn't stall until the response arrives and there is no way to make that happen. What instead happens is that sync code is continued to be executed (print("3")).
If your currently executed sync code reaches its end, then the next scheduled task is processed the same way.
then() schedules the code (json) { print("2"); } to be executed after getString() completed.
await
async and await just make async code look more like sync code but otherwise it is quite the same and will be translated under the hood to xxx.then((y) { ... }).
I'm not sure I understand what you're trying to achieve, but it looks to me you want to do this:
myFunction() async {
print("1");
final json = await HttpRequest.getString(url);
print("2");
print("3");
}
async statement is only needed in the consumer function. In other words, producer functions doesn't need to have async, they only need to return a Future.
you should be able to do this:
Future consumerFunc() async {
print("1");
var response = await HttpRequest.getString(url);
print("2");
print("3");
}
and it should result:
1
2
3
Note: await replaces then methods

Resources