Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making a Future block until it's done

Tags:

dart

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.

like image 646
Jan Vladimir Mostert Avatar asked Jan 06 '23 22:01

Jan Vladimir Mostert


1 Answers

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

like image 88
Günter Zöchbauer Avatar answered Jan 10 '23 19:01

Günter Zöchbauer