Dart Async Do Something Else Then Wait

Issue

I’ve read several stackoverflow questions, dart documents, and even watched a video on async and await. I haven’t found an answer to my question. I would like to call an async method, execute other code, and then wait on the completion of the async task.

Here’s an example of what I’m working with. This is my component

Credit credit;
...
Future<Null> getCredit(id) async {
  try {
    credit = await _creditService.getCredit(id);
  }
  catch (e) {
    errorMessage = e.toString();
  }
}
...
void onUpdateCredit(int credit_id) {
  getCredit(credit_id);
  creditDialogTitle = 'Update Credit';
  creditArtistIndex = credit.artist_id;
  instrument = credit.instrument;
  creditNotes = credit.notes;
  creditDialog.open();
}

This code crashes because credit is null when an attempt is made to use it. One way around it is combine the two methods:

Future<Null> onUpdateCredit(id) async {
  try {
    credit = await _creditService.getCredit(id);
    creditDialogTitle = 'Update Credit';
    creditArtistIndex = credit.artist_id;
    instrument = credit.instrument;
    creditNotes = credit.notes;
    creditDialog.open();
  }
  catch (e) {
    errorMessage = e.toString();
  }
}

Nothing is done is parallel and, if I need the credit some where else in my code, I would have to duplicate the try/catch portion of the method. I could also code it like this:

void onUpdateCredit(int credit_id) {
  credit = null;
  getCredit(credit_id);
  creditDialogTitle = 'Update Credit';
  while (credit == null) {//wait a period of time}
  creditArtistIndex = credit.artist_id;
  instrument = credit.instrument;
  creditNotes = credit.notes;
  creditDialog.open();
}

In other situations, I do something similar to this in my html with *ngIf=”var != null” where var is populated by a future.

Is there a better way than using while (credit == null) ? This example only executes one instruction between the request and the completion so is trivial. I’m sure I’ll other situations where I have a lot to do in between. I’m also adding the service method:

Future<Credit> getCredit(int id) async {
  try {
    String url = "http://catbox.loc/credits/${id.toString()}";
    HttpRequest response = await HttpRequest.request(
        url, requestHeaders: headers);
    Map data = JSON.decode(response.responseText);
    final credit = new Credit.fromJson(data);
    return credit;
  }
  catch (e) {
    throw _handleError(e);
  }
}

Update

Based on @Douglas’ answer, this works:

Future<Null> onUpdateCredit(id) async {
  Future future = getCredit(id);
  creditDialogTitle = 'Update Credit';
  await future;
  creditArtistIndex = credit.artist_id;
  instrument = credit.instrument;
  creditNotes = credit.notes;
  creditDialog.open();
}

I then eliminated the intervening method.

Future<Null> onUpdateCredit(id) async {
  try {
    Future<Credit> future =  _creditService.getCredit(id);
    creditDialogTitle = 'Update Credit';
    credit = await future;
    creditArtistIndex = credit.artist_id;
    instrument = credit.instrument;
    creditNotes = credit.notes;
    creditDialog.open();
  }
  catch (e) {
    errorMessage = e.toString();
    }
}

Solution

getCredit(credit_id) does not just kick off an asynchronous call, it also returns a Future object – immediately. Store that object in a local variable, and you can use it later to asynchronously execute additional code when it completes.

There are two ways to use that Future object. The easier and more fluent way requires that you declare onUpdateCredit to be async. Inside an async function, the line await futureObject will cause all code after that line to be executed asynchronously after the Future completes. The complete version of onUpdateCredit using this technique would look like this:

Future<Null> onUpdateCredit(int credit_id) async {
  Future future = getCredit(credit_id);
  creditDialogTitle = 'Update Credit';
  await future;
  creditArtistIndex = credit.artist_id;
  instrument = credit.instrument;
  creditNotes = credit.notes;
  creditDialog.open();
}

The other way is to explicitly register the rest of your code as a callback using .then(). That would look like this:

void onUpdateCredit(int credit_id) {
  Future future = getCredit(credit_id);
  creditDialogTitle = 'Update Credit';
  future.then((_) => {
    creditArtistIndex = credit.artist_id;
    instrument = credit.instrument;
    creditNotes = credit.notes;
    creditDialog.open();
  });
}

Note that in either case, if the exception path occurs in getCredit(id) you will get errors for credit not being set. If you truly want the exception to be swallowed silently, you should have its handler fill in a default value for credit so that code that assumes it completed normally will still work.

Also note that your while loop version would fail – Dart, like JavaScript, is not truly multithreaded, and busy waiting like that will block the event loop forever, preventing the code that would set credit from ever running.

A short summary of how async and await work in general:

Future someFunc(args) async {
  ...
  return value;
}

is equivalent to:

Future someFunc(args) {
  return new Future(() => {
    ...
    return value;
  }
}

The code inside gets executed on a later iteration of the event loop, and the returned future completes either successfully with value or exceptionally with anything thrown in that code.

Meanwhile this:

try {
  value = await someFutureObject;
  ...more code here...
} catch (e) {
  ...exception handling here...
}

is equivalent to:

someFutureObject.then((value) => {
  ...more code here...
}).catchError((e) => {
  ...exception handling here...
});

The most common use case is someVar = await someAsyncCall();, but you can save the Future itself by omitting await, or you can await on an arbitrary Future object no matter where it comes from.

However – and this is what makes await and async so incredibly convenient – you can have 5 different exit points from the async function interspersed with three await calls (in the same async function) in assorted places inside 13 nested loops, switches, try/catch, and if blocks, and Dart will automatically figure out the necessary tree of callbacks to make it all follow the same code path as if all the calls were synchronous.

Answered By – Douglas

Answer Checked By – Clifford M. (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *