16

My data is something like this:

{
  "five": {
    "group": {
      "one": {
        "order": 2
      },
      "six": {
        "order": 1
      }
    },
    "name": "Filbert",
    "skill": "databases"
  },
  "four": {
    "group": {
      "three": {
        "order": 2
      },
      "two": {
        "order": 1
      }
    },
    "name": "Robert",
    "skill": "big data"
  },
  "one": {
    "name": "Bert",
    "skill": "data analysis"
  },
  "seven": {
    "name": "Colbert",
    "skill": "data fudging"
  },
  "six": {
    "name": "Ebert",
    "skill": "data loss"
  },
  "three": {
    "name": "Gilbert",
    "skill": "small data"
  },
  "two": {
    "name": "Albert",
    "skill": "non data"
  }
}

I am using the following function:

  Future retrieve(String id) async {
    Map employeeMap = await employeeById(id); //#1
    if (employeeMap.containsKey("group")) { //#2
      Map groupMap = employeeMap["group"];
      Map groupMapWithDetails = groupMembersWithDetails(groupMap); #3
      // above returns a Mamp with keys as expected but values 
      // are Future instances.
      // To extract values, the following function is
      // used with forEach on the map
      futureToVal(key, value) async { // #4
        groupMapWithDetails[key] = await value; 
      }
      groupMapWithDetails.forEach(futureToVal); // #4
    }
    return groupMapWithDetails;
   }
  1. I access an employee (as aMap) from the database (Firebase)
  2. If the employee is a leader (has a key "group")
  3. I get the details of each of the employee in the group filled up from the database by calling a separate function.
  4. Since the function returns a map with values that are instances of Future, I want to extract the actual values from them. For this a forEach is called on the map. However, I only get instances of Future as values.

How can I get the actual values?

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
Hesh
  • 413
  • 1
  • 3
  • 11

2 Answers2

60

There is no way to get back from async execution to sync execution.

To get a value from a Future there are two ways

pass a callback to then(...)

theFuture.then((val) {
  print(val);
});

or use async/await for nicer syntax

Future foo() async {
  var val = await theFuture;
  print(val);
}
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 2
    Is there really NO WAY to access the value of Dart Future ? Something similar to SCALA's `Await.result(someFuture, 5.seconds)` ??? – kosiara - Bartosz Kosarzycki Sep 12 '19 at 07:56
  • @kosiara-BartoszKosarzycki I don't understand your question. You can use `await` as shown above. There is no way to get the value synchronically though because otherwise it wouldn't have been made async in the first place. – Günter Zöchbauer Sep 12 '19 at 09:10
  • 1
    I'm looking for a way to convert Future to a synchronous call. As in the previous example from SCALA - the call waits for a Future's value for 5 sec and fails if it cannot get future value. I.e. `var synchedVal = Await.result(someFuture, 5.seconds)` and then `synchedVal` CAN be returned from a non-async function - as a Type and NOT as Future. In the await example you'd given the value CAN be used ONLY in the scope of the async function. – kosiara - Bartosz Kosarzycki Sep 12 '19 at 09:19
  • 1
    There is no such thing in Dart. What Scala does looks like an ugly hack. That might work if you have threads, but Dart does not have threads and Dart was also built to be compilable to JS and in JS there is no way to implement such a "hack". – Günter Zöchbauer Sep 12 '19 at 09:31
  • I never use printing. What about actually returning the value? – samuelnehool Oct 18 '22 at 10:40
  • @samuelnihoul see the first line of my answer. If you return the value, it's still wrapped in a Future. Future is just a wrapper over a callback. If you call a callback and pass a value, you also can't access the value from the code that passed the callback reference. When the callback is called, the execution of the code that passed the callback has long been completed already. – Günter Zöchbauer Nov 01 '22 at 15:20
  • But there's the await keyword :-( – samuelnehool Nov 03 '22 at 06:44
  • @samuelnihoul it's just simplified syntax. You can't do anything using `await` that you can't do without `await`. it's just `var result = await someAsync();` instead of `someAsync().then((result) { ... });` – Günter Zöchbauer Nov 03 '22 at 15:45
4

You are not waiting for the await value expressions to complete.

The forEach call runs through the map entries and starts an asynchronous computation for each. The future of that computation is then dropped because forEach doesn't use the return value of its function. Then you return the map, far before any of the asynchronous computations have completed, so the values in the map are still futures. They will eventually change to non-futures, but you won't know when it's done.

Instead of the forEach call, try:

await Future.wait(groupMapWithDetails.keys.map((key) async {
  groupMapWithDetails[key] = await groupMapWithDetails[key];
});

This performs an asynchronous operation for each key in the map, and the waits for them all to complete. After that, the map should have non-future values.

lrn
  • 64,680
  • 7
  • 105
  • 121