Home > OS >  Using async value in another function (Dart)
Using async value in another function (Dart)

Time:04-18

Here's the simplifed code to show what I'm trying to achieve:

class MyClass {
  MyClass() {
    authorize();
  }

  String? token;

  void authorize() async {
    final response =
        await Future<String>.delayed(Duration(seconds: 1), (() => "14dw65d1q"));
    token = response;
    print('Token: $token');
  }

  String printToken() {
    return 'Token: $token';
  }
}

void main() {
  final myClass = MyClass();
  print(myClass.printToken());
}

Basically I'm trying to use token (whose default value of null is changed by authorize()) in another function. The output is:

Token: null
Token: 14dw65d1q

Apparently, main() runs before the value of token gets assigned. Thus, I get a 401 error when I try to use token in the header of a network request. (Not shown in code) How can I make printToken() run after the value of token gets assigned? Making main() async and/or awaiting myClass.printToken() doesn't work. I get 'await' applied to 'String', which is not a 'Future'. when I await 'Token: $token' or myClass.printToken() anyway.

CodePudding user response:

Your MyClass implementation does not provide any Futures to its consumers, and consumers therefore will not be able to be notified when asynchronous operations in MyClass complete. There are two specific problems:

  1. You've made the authorize method "fire-and-forget" by returning void. That instead should return a Future<void> to allow callers to be notified when it completes.

  2. You call authorize from within the MyClass constructor. However, invoking a constructor must always result in an instance of the class, not a Future.

Possible fixes for problem #2:

  • Construct MyClass with a static method that returns a Future.

    class MyClass {
      MyClass._();
    
      static Future<MyClass> createInstance() async {
        var instance = MyClass._();
        await instance.authorize();
        return instance;
      }
    
      String? token;
    
      Future<void> authorize() async {
        final response =
            await Future<String>.delayed(Duration(seconds: 1), (() => "14dw65d1q"));
        token = response;
        print('Token: $token');
      }
    
      String printToken() {
        return 'Token: $token';
      }
    }
    
    void main() async {
      final myClass = await MyClass.createInstance();
      print(myClass.printToken());
    }
    
  • Use a Completer.

    import 'dart:async';
    
    class MyClass {
      MyClass() {
        authorize();
      }
    
      String? token;
      final _completer = Completer<void>();
    
      Future<void> get authorizationDone => _completer.future;
    
      Future<void> authorize() async {
        final response =
            await Future<String>.delayed(Duration(seconds: 1), (() => "14dw65d1q"));
        token = response;
        _completer.complete();
        print('Token: $token');
      }
    
      String printToken() {
        return 'Token: $token';
      }
    }
    
    void main() async {
      final myClass = MyClass();
      await myClass.authorizationDone;
      print(myClass.printToken());
    }
    
  • Move asynchronous operations out of the constructor.

    class MyClass {
      MyClass();
    
      String? token;
    
      Future<void> authorize() async {
        final response =
            await Future<String>.delayed(Duration(seconds: 1), (() => "14dw65d1q"));
        token = response;
        print('Token: $token');
      }
    
      String printToken() {
        return 'Token: $token';
      }
    }
    
    void main() async {
      final myClass = MyClass();
      await myClass.authorize();
      print(myClass.printToken());
    }
    

I recommend using the first approach since it's less error-prone for callers and since you usually shouldn't be using Completer directly.

  •  Tags:  
  • dart
  • Related