Dart - Distinguish between sync and async void callbacks - asynchronous

Suppose we have the following method:
Future<String> foo(void callback()) async{
...
callback();
...
}
Then the callback is allowed to be either sync or async when defined. Is there a clean way to await it if it is async? Now if I await it here I get this warning from IDE:
'await' applied to 'void', which is not a 'Future'

You can't. The whole point of making an async function return void instead of Future<void> is so that the function is "fire-and-forget" and so that the caller can't wait for it to complete.
If you want to allow your foo function to take asynchronous functions as arguments, then its parameter should be a Future<void> Function() instead. That way callers can easily adapt a synchronous function with an asynchronous wrapper. (The reverse is not possible.)
Alternatively you could make the callback parameter return a FutureOr<void>:
Future<String> foo(FutureOr<void> Function callback) async {
...
await callback();
...
}
or if you don't want an unnecessary await if callback is synchronous:
Future<String> foo(FutureOr<void> Function callback) async {
...
if (callback is Future<void> Function) {
await callback();
} else {
callback();
}
...
}

Related

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!";});
}

Parse Cloud Code response is null after calling a function with callback

I have a cloud code from which I call an external function.
The cloud code response is null but the console displays the response
my cloud code ;
Parse.Cloud.define("testccadd", async request => {
try {
var ccaddrequest = {
conversationId: '123456789',
email: 'email#email.com',
};
externalFunction (ccaddrequest, function (err, result) {
console.log(result);
return result;
}) ;
} catch (e) {
console.log("Error");
}
});
console.log (result); shows the values from the external function, but the return result; returns null
how can I get the external function response as response of my cloud code function ?
The problem is that your externalFunction uses a callback to return its result. That is an asynchronous event, meaning that it happens after your cloud functions has been processed.
The cloud function will execute var ccaddrequest... and then call externalFunction but it won't "wait" for externalFunction to call the callback function if it contains asynchronous commands.
So you need to wrap the externalFunction in a Promise (see how to promisify callbacks) and then await the result of it.
Plus you need to return the result of the Promise, so in your code you need to add
Parse.Cloud.define("testccadd", async request => {
try {
var ccaddrequest = {
conversationId: '123456789',
email: 'email#email.com',
};
var result = await externalFunctionPromise(...);
return result;
} catch (e) {
console.log("Error");
}
});

How can I correctly yield from a stream that uses Futures/async/await?

I've encountered a weird issue where if I yield* from my provider in my flutter app, the rest of the code in the function doesn't complete.
I'm using the BLoC pattern, so my _mapEventToState function looks like this:
Stream<WizardState> _mapJoiningCongregationToState(
int identifier, int password) async* {
_subscription?.cancel();
_subscription= (_provider.doThings(
id: identifier, password: password))
.listen((progress) => {
dispatch(Event(
progressMessage: progress.progressText))
}, onError: (error){
print(error);
}, onDone: (){
print('done joiining');
});
}
Then in the provider/service... this is the first attempt.
final StreamController<Progress> _progressStream = StreamController<JoinCongregationProgress>();
#override
Stream<JoinCongregationProgress> doThings(
{int id, int password}) async* {
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake1..."));
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake5!!!..."));
yield* _progressStream.stream;
}
The yield statement returns, but only after both awaited functions have completed. This makes complete sense to me, obviously I wouldn't expect the code to complete out of order and somehow run the yield* before waiting for the 'await's to complete.
In order to "subscribe" to the progress of this service though, I need to yield the stream back up to the caller, to write updates on the UI etc. In my mind, this is as simple as moving the yield* to before the first await. Like this.
final StreamController<Progress> _progressStream = StreamController<JoinCongregationProgress>();
#override
Stream<JoinCongregationProgress> doThings(
{int id, int password}) async* {
yield* _progressStream.stream;
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake1..."));
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake5!!!..."));
}
But, then setting breakpoints on the later _progressStream.add calls show that these never get called. I'm stuck on this, any idea what it could be? I know it has something to do with how I have mixed Futures and Streams.
The yield* awaits the completion of the stream it returns.
In this case, you want to return a stream immediately, then asynchronously feed some data into that stream.
Is anything else adding events to the stream controller? If not, you should be able to just do:
#override
Stream<JoinCongregationProgress> doThings({int id, int password}) async* {
await Future.delayed(Duration(seconds:2));
yield JoinCongregationProgress(progressText: "kake1...");
await Future.delayed(Duration(seconds:2));
yield JoinCongregationProgress(progressText: "kake5!!!...");
}
No stream controller is needed.
If other functions also add to the stream controller, then you do need it. You then have to splut your stream creation into an async part which updates the stream controller, and a synchronous part which returns the stream. Maybe:
final StreamController<Progress> _progressStream = StreamController<JoinCongregationProgress>();
#override
Stream<JoinCongregationProgress> doThings({int id, int password}) {
() async {
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake1..."));
await Future.delayed(Duration(seconds:2));
_progressStream.add(JoinCongregationProgress(progressText: "kake5!!!..."));
}(); // Spin off async background task to update stream controller.
return _progressStream.stream;
}

Flutter : Question about Futures and async function

I have several questions regarding the use of Futures in dart. Let's say I am working with a firestore and I have a function like this to update users's info :
void updateOldUser(User oldUser,String newInfo){
DocumentReference userToUpdateRef = userRef.document(oldUser.id);
Firestore.instance.runTransaction((Transaction transaction) async {
DocumentSnapshot userToUpdateSnapshot = await transaction.get(userToUpdateRef);
if(userToUpdateSnapshot.exists){
await transaction.update(
userToUpdateSnapshot.reference, userToUpdateSnapshot.data[newInfo] + 1
).catchError((e)=> print(e));
}
});
}
My question is : does it need to return a future since runTransaction is Future. It seems to work fine without it but to me it feels like it should return a "Future void" in order to be able to await updateOldUser when I use it. But when I turn it into a "Future void" and then end the function body with 'return;' I have an error saying 'expected a value after return'.
But what I really don't understand is that with another similar code :
Future<void> updateUserPhoto(User user,File userPhoto) async {
String photoUrl = await uploadImage(user.id,userPhoto);
DocumentReference userToUpdateRef = userRef.document(user.id);
Firestore.instance.runTransaction((Transaction transaction) async {
DocumentSnapshot userToUpdateSnapshot = await transaction.get(userToUpdateRef);
if(userToUpdateSnapshot.exists){
await transaction.update(
userToUpdateSnapshot.reference, {
'photoUrl' : photoUrl
}
).catchError((e)=> print(e));
}
});
return;
}
I don't get this error and It work fine also. Why ? Thanks in advance.
does it need to return a future
If you want the caller to be able to await the completion of the function, then the return type should be Future<...> (like Future<void> if there is no concrete return value, or Future<int> if the result is an integer value, ...)
For fire-and-forget async functions you can use void, but that is rather uncommon.
If the return type is Future<...> the caller can still decide not to wait for completion anyway.
But when I turn it into a "Future void" and then end the function body with 'return;'
If you use async, a Future is returned implicitly at the end of the method.
If you do not use async, you need to return a Future like
return doSomething.then((v) => doSomethingElse());

In a Dart async method, can certain lines that don't share any dependencies be run asynchronously?

Suppose you have an async method body, like below, with a method call that returns a Future and a print following it that doesn't not need the result of that method. Is it possible to add some structure to get the print statement to execute independently? Or does this syntactic sugar force an async method body to wholly run sequentially?
Future<String> test2() {
Completer c = new Completer();
new Timer(new Duration(seconds: 2), () {
c.complete('test2: done');
});
return c.future;
}
Future<String> test1() async {
String result = await test2();
print(result);
// Why is this not executed immediately before test2 call completes?
print('hello');
return 'test1: done';
}
void main() {
test1().then((result) => print(result));
}
Follow up: I've added below a rewrite of test1() which has chained async method calls. I'm really after how to use the async syntactic sugar to simplify this type of use case. How could I rewrite this block with the new syntax?
Future<String> test1() async {
test2().then((result) {
print(result);
test2().then((result) {
print(result);
});
});
// This should be executed immediately before
// the test2() chain completes.
print('hello');
return 'test1: done';
}
edit for follow up:
I am not sure what you want to do after all, but if you want wait for the chain to finish before printing things, here's what you have to do:
Future<String> test1() async {
String result;
var result1 = await test2();
print("Printing result1 $result1 ; this will wait for the first call to test2() to finish to print");
var result2 = await test2();
print("Printing result2 $result2 ; this will wait for the second call to test2() to print");
// This should be executed immediately before
// the test2() chain completes.
print('hello');
return 'test1: done';
}
Now if you call test1() and wait for it to return "test1: done" you have to await it.
main() async {
var result = await test1();
print(result); // should print "hello" and "test1: done"
}
If you want to execute code independently from a previous future result, just don't put the await keyword.
Future<String> test1() async {
test2().then((String result) => print(result)); // the call to test2 will not stop the flow of test1(). Once test2's execution will be finished, it'll print the result asynchronously
// This will be printed without having to wait for test2 to finish.
print('hello');
return 'test1: done';
}
The keyword await makes the flow stalled until test2() is finished.
This is because of the await before test2();. The execution is stalled until the Future returned by test2() is completed.

Resources