Search code examples
dartindexeddbdart-async

How to return a function value from inside an inner function/stream listener?


I was writing a function in dart that would delete an object from a browser-side Indexed DB, when I discovered that I had to return an outer function value from within an inner function:

Future<bool> delete() {
    Transaction tx = db.transactionStore(storeName, "readwrite");
    ObjectStore os = tx.objectStore(storeName);
    os.delete(_key); // returns blank future, modifies tx

    // This is not correct, but shows the idea:
    if (tx.onComplete) {return true;}
    if (tx.onError) {return false;}
}

This function is a method for a class that I am using to save and load to the Indexed DB. I want this function to return true or false, or a Future object containing the same, when the delete operation succeeds or fails. However, the bottleneck is the os.delete(_key); statement: it returns a future, but the actual success or failure of the delete operation is provided by tx.onComplete and tx.onError. Both of these Objects are streams, so I need to create anonymous functions that handle events from them:

tx.onComplete.listen((e){
    return_to_outer_function(true);
});
tx.onError.listen((e){
    return_to_outer_function(false);
});
return_to_outer_function(bool) {
    return bool; // doesn't work
}

As you can see, when I create anonymous functions, the return statement no longer completes the method, but the inner function. I could have the inner functions call other functions, but then those other functions have return statements of their own that don't return a result to the whole method.

I tried the approach of setting temporary variables and periodically checking them, but it's a very inelegant solution that I don't want to have to use, not just for potential bugs, but because it would hog up the single threaded event loop.

Is it possible to return a value to an outer function from an inner function? Or is there some other, better way to get a value from the presence or absence of events from a set of streams? Or is there another way of using IndexedDB that will avoid this problem?


Solution

  • You can use a Completer for this.

    Future<bool> delete() {
      Completer completer = new Completer();
      Transaction tx = db.transactionStore(storeName, "readwrite");
      ObjectStore os = tx.objectStore(storeName);
    
      tx.onError.first((e){
        //return_to_outer_function(false);
        completer.complete(false);
      });
      tx.onComplete.first(bool) {
        //return bool; // doesn't work
        completer.complete(true)
      }
      os.delete(_key); // register listeners and then do delete to be on the save side
    
      return completer.future;
    }
    

    you then call it like

    delete().then((success) => print('succeeded: $success'));
    

    see also https://api.dartlang.org/apidocs/channels/be/dartdoc-viewer/dart:async.Completer