flutterdartasync-awaitcasting

await or not to await, that's the question


The following Dart code totally functional:

  Future<Result<bool, String>> downloadFileFrom(String url) async {
    try {
      const defaultDir = '/sdcard/Download';
      final [targetDir as String, hasPermission as bool] = await Future.wait([
        getExternalStorageDirectory().then((storage) => storage?.path ?? defaultDir),
        _checkPermission(),
      ]);

      return !hasPermission
        ? const Ok(false)
        : FlutterDownloader.enqueue(
            url: url,
            savedDir: targetDir,
            showNotification: true,
            openFileFromNotification: true,
            saveInPublicStorage: true,
        ).then((taskId) => Ok(taskId != null))
      ;
    } catch (e) {
      return Err(e.toString());
    }
  }

I wish to know there is any difference when returning FlutterDownloader.enqueue with wait or without it. If I shouldn't use wait, then shouldn't I return Future.value(Ok(false)) too?


Solution

  • From @Abion47's and @Rico's answers I wanted to go a little deeper into the topic, so I generated the following code:

    Future<String> ffuncA([bool useConst = false, int ms = 1000, bool throwError = false]) async {
      return useConst
        ? 'const_A'
        : await Future.delayed(
            Duration(milliseconds: ms), 
            () {
              if (throwError) throw Exception('Error in ffuncA');
              return 'value_A ($ms)';
            }
          );
    }
    
    Future<String> ffuncB([bool useConst = false, int ms = 1000, bool throwError = false]) async {
      return useConst
        ? 'const_B'
        : Future.delayed(
            Duration(milliseconds: ms), 
            () {
              if (throwError) throw Exception('Error in ffuncB');
              return 'value_B ($ms)';
            }
          );
    }
    
    Future<String> ffuncC([bool useConst = false, int ms = 1000, bool throwError = false]) {
      return useConst
        ? Future.value('const_C')
        : Future.delayed(
            Duration(milliseconds: ms), 
            () {
              if (throwError) throw Exception('Error in ffuncC');
              return 'value_C ($ms)';
            }
          );
    }
    
    List<int> getTimes(bool flag) => flag ? List.filled(3, 1000) : List.generate(3, (i) => 1500 - i*250);
    
    Future<void> withAwait({bool useConst = false, bool useDefaultTime = true, bool throwError = false}) async {
      try {
        final times = getTimes(useDefaultTime);
        print('Calling futures...');
        print('... A');
        await ffuncA(useConst, times[0], throwError).then(print);
        print('... B');
        await ffuncB(useConst, times[1], throwError).then(print);
        print('... C');
        await ffuncC(useConst, times[2], throwError).then(print);
        print('Done!');
      } catch (e) {
        print('Error catched in try/catch block: $e');
      }
    }
    
    void withoutAwait({bool useConst = false, bool useDefaultTime = true, bool throwError = false}) {
      try {
        final times = getTimes(useDefaultTime);
        print('Calling futures...');
        print('... A');
        ffuncA(useConst, times[0], throwError).then(print);
        print('... B');
        ffuncB(useConst, times[1], throwError).then(print);
        print('... C');
        ffuncC(useConst, times[2], throwError).then(print);
        print('Done!');    
      } catch (e) {
        print('Error catched in try/catch block: $e');    
      }
    }
    
    Future<void> withAwaitOnError({bool useDefaultTime = true}) async {
      try {
        final times = getTimes(useDefaultTime);
        print('Calling futures...');
        print('... A');
        await ffuncA(false, times[0], true).then(print).onError((e, _) => print('Error catched with onError: $e'));
        print('... B');
        await ffuncB(false, times[1], true).then(print).onError((e, _) => print('Error catched with onError: $e'));
        print('... C');
        await ffuncC(false, times[2], true).then(print).onError((e, _) => print('Error catched with onError: $e'));
        print('Done!');
      } catch (e) {
        print('Error catched in try/catch block: $e');
      }
    }
    
    void withoutAwaitOnError({bool useDefaultTime = true}) {
      try {
        final times = getTimes(useDefaultTime);
        print('Calling futures...');
        print('... A');
        ffuncA(false, times[0], true).then(print).onError((e, _) => print('Error catched with onError: $e'));
        print('... B');
        ffuncB(false, times[1], true).then(print).onError((e, _) => print('Error catched with onError: $e'));
        print('... C');
        ffuncC(false, times[2], true).then(print).onError((e, _) => print('Error catched with onError: $e'));
        print('Done!');    
      } catch (e) {
        print('Error catched in try/catch block: $e');    
      }
    }
    

    The code for main function will be something like this:

    void main(List<String> args) {
      print('>>>> BEGIN MAIN');
      // code line here!!!!
      print('>>>> END MAIN');
    }
    

    Let's try every case:

    withAwait();

    >>>> BEGIN MAIN
    Calling futures...
    ... A
    >>>> END MAIN
    value_A (1000)
    ... B
    value_B (1000)
    ... C
    value_C (1000)
    

    withoutAwait();

    >>>> BEGIN MAIN
    Calling futures...
    ... A
    ... B
    ... C
    >>>> END MAIN
    value_A (1000)
    value_B (1000)
    value_C (1000)
    

    withAwait(useDefaultTime: false);

    >>>> BEGIN MAIN
    Calling futures...
    ... A
    >>>> END MAIN
    value_A (1500)
    ... B
    value_B (1250)
    ... C
    value_C (1000)
    

    withoutAwait(useDefaultTime: false);

    >>>> BEGIN MAIN
    Calling futures...
    ... A
    ... B
    ... C
    >>>> END MAIN
    value_C (1000)
    value_B (1250)
    value_A (1500)
    

    withAwait(throwError: true);

    >>>> BEGIN MAIN
    Calling futures...
    ... A
    >>>> END MAIN
    Error catched in try/catch block: Exception: Error in ffuncA
    

    withoutAwait(throwError: true);

    >>>> BEGIN MAIN
    Calling futures...
    ... A
    ... B
    ... C
    >>>> END MAIN
    Unhandled exception:
    Exception: Error in ffuncA
    #0      ffuncA.<anonymous closure> (file:///home/.../demo/main.dart:7:27)
    #1      new Future.delayed.<anonymous closure> (dart:async/future.dart:419:42)
    #2      Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:18:15)
    #3      _Timer._runTimers (dart:isolate-patch/timer_impl.dart:410:19)
    #4      _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:441:5)
    #5      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:193:12)
    
    Exited (255).
    

    withAwaitOnError();

    >>>> BEGIN MAIN
    Calling futures...
    ... A
    >>>> END MAIN
    Error catched with onError: Exception: Error in ffuncA
    ... B
    Error catched with onError: Exception: Error in ffuncB
    ... C
    Error catched with onError: Exception: Error in ffuncC
    

    withoutAwaitOnError();

    >>>> BEGIN MAIN
    Calling futures...
    ... A
    ... B
    ... C
    >>>> END MAIN
    Error catched with onError: Exception: Error in ffuncA
    Error catched with onError: Exception: Error in ffuncB
    Error catched with onError: Exception: Error in ffuncC
    

    Now I know there are three ways to write a future return, and when to use onError. Therefore my original code just needs to add onError toFlutterDownloader.enqueue and it would be correct.