Brian Oh
Brian Oh

Reputation: 10720

Async Futures running in sequence to completion

I encountered the following example (Example 1 below) of Futures which caused me to wonder if I could alter the way that I was handling Futures and remove all of the nested function calls that preserve order of processing, which however result in indentation which I find a bit messy.

The altered version of my program did not work however. It did not preserve the order of processing and did not “wait” for function to complete. For example, before returning from the first call (fGetUserInput), another subsequent function was called.

Why is it that in Example 1, all of the “1st level” “new Future”s processed sequentially, however in Example 2, my altered code, the order of processing is not preserved. While the call to fGetUserInput is being processed, one of the Futures that follows it is processed?

Is it perhaps that “Example 1” only “works” because all of the statements are synchronous?

I came across a reference to “runAsync”. Can that be used to achieve what I want? (process in sequence without all of the indentation).

    // Example 1. Code that I encountered  for Futures //

import 'dart:async';

main() {

  new Future(() => print('1'))
   .then((_) => print('a'))
   .then((_) => print('b'));

  new Future(() => print('2'))
   .then((_) => print('c'))
   .then((_) => print('d'));

  new Future(() => print('3'))
   .then((_) => 
       new Future(() => print('e'))
       .then((_) => print('f'))
   );

  new Future(() => print('4'))
   .then((_) => 
       new Future(() => print('g'))
       .then((_) => print('d'))
   );
}

The above results in the following console output order :-

1   a   b   2   c   d   3   4   e   f   g   d

Which I thought made sense.

Therefore, I modified my code to test it as follows :-

   // Example 2. Altered version of my code which //
   // does not preserve the order of processing,  //
   // which is necessary for program to function. //

  new async.Future(() => fGetUserInput())
  .then((lInput) {
    iMaxIters = int.parse(lInput[4]);
    tClearTable = (lInput[5] == "y");

    iDivisor = fInitialize(iMaxIters);

    tgPrint = false;  // printing off

    sUri =
     "postgres://${lInput[1]}:${lInput[2]}@localhost:5432/${lInput[3]}";
    sStartTime = lInput[7];
  })
  .catchError((oError) => fFatal("Get User Input", oError));

  new async.Future(() => fConnectToDb(sUri, sStartTime))
  .then((bool tConnected) {
    if (ogDb == null)
      fFatal("Unable to connect to database", "");
    print ("Processing database ......");
  })
  .catchError((oError) => fFatal("Connect to Db", oError));

  new async.Future(() => fClearTable(tClearTable))
  .then((sResult) => print (sResult+"\n"))
  .catchError((oError) => fFatal("Clear Table", oError)); 

  new async.Future(() => fProcessInserts(iMaxIters, iDivisor))
  .then((sResult) => print (""))
  .catchError((oError) => fFatal("Process Inserts", oError));

  new async.Future(() => fSetupRandKeys())
  .then((sResult) => print (""))
  .catchError((oError) => fFatal("Setup Random Keys", oError));

  new async.Future(() => fProcessUpdates(iMaxIters, iDivisor))
  .then((sResult) {
    String sTotValue = fFormatAmount(igGrandTotAmt, true, 2);
    fPrint ("Grand Total added to database = \$${sTotValue}");
    ogDb.close();
    exit(0);
  })
  .catchError((oError) => fFatal("Process Updates", oError));
}  

void fFatal (String sMessage, Error oError) {
  print("\n\nFatal Error. $sMessage\n${oError}");
  exit(1);
}

async.Future<String> fProcessInserts(int iMaxIters, int iDiv) {
  async.Completer oCompleter = new async.Completer<String>();

  int iTot = 0;
  Function fLoop;

  print ("\nProcessing Inserts ......");
  fResetAndStartWatch();

The following is my code prior to the above changes, and the following Example 3 appears to work OK. I don't like the extent of indentation, and in situations with more function calls, that would increase the extent of indentation. I was hoping for a more elegant way to do it.

    // Example 3: The original version of my code //
    // which does preserve the order of processing //
void main() {

  print("");

  String sCheckPoint = "Get User Input";
  fGetUserInput()
  .then((lInput) {
    int iMaxIters = int.parse(lInput[4]);
    bool tClearTable = (lInput[5] == "y");

    int iDiv = fInitialize(iMaxIters);

    tgPrint = false;  // printing off

    String sUri =
      "postgres://${lInput[1]}:${lInput[2]}@localhost:5432/${lInput[3]}";

    sCheckPoint = "Connect to Database";
    fConnectToDb(sUri, lInput[7]).then((bool tConnected) {
      if (ogDb == null)
        fFatal(sCheckPoint, "Unable to conenct to Db");
      print ("Processing database ......");
      sCheckPoint = "Clear Table";
      fClearTable(tClearTable).then((sResult) {
        print (sResult+"\n");
        sCheckPoint = "Process Inserts";
        fProcessInserts(iMaxIters, iDiv).then((sResult) {
          print;
          sCheckPoint = "Set-up Random Keys";
          fSetupRandKeys().then((sResult) {
            print;
            sCheckPoint = "Process Updates";
            fProcessUpdates(iMaxIters, iDiv).then((sResult) {
              String sTotValue = fFormatAmount(igGrandTotAmt, true, 2);
              fPrint ("Grand Total added to database = \$${sTotValue}");
              ogDb.close();
              exit(0);
            });
          });
        });
      });
    });
  })
  .catchError((oError) => fFatal(sCheckPoint, oError));
}

void fFatal (String sMessage, Error oError) {
  print("\n\nFatal Error. $sMessage\n${oError}");
  exit(1);
}

async.Future<String> fProcessInserts(int iMaxIters, int iDiv) {
  async.Completer oCompleter = new async.Completer<String>();

  int iTot = 0;
  Function fLoop;

  print ("Processing Inserts ......");
  fResetAndStartWatch();

Upvotes: 1

Views: 369

Answers (1)

Justin Fagnani
Justin Fagnani

Reputation: 11171

Remember that you can chain futures, which will reduce your indentation by quite a bit.

The downside is that you don't get nested scopes, which can be useful if you have more than one value to propagate between async blocks, but that can be worked around in a few ways.

Here's you example 3 with chaining:

// Example 3 with chaining
void main() {
  String checkPoint = "Get User Input";
  getUserInput().then((input) {
    int maxIters = int.parse(input[4]);
    bool clearTable = (input[5] == "y");

    int div = initialize(maxIters);

    shouldPrint = false;  // printing off

    String uri =
        "postgres://${input[1]}:${input[2]}@localhost:5432/${input[3]}";

    checkPoint = "Connect to Database";
    return connectToDb(uri, input[7]).then((bool connected) {
      if (db == null)
          fatal(checkPoint, "Unable to conenct to Db");
      print ("Processing database ......");
      checkPoint = "Clear Table";
      return clearTable(shouldClearTable);
    }).then((result) {
      print (result+"\n");
      checkPoint = "Process Inserts";
      return processInserts(maxIters, div);
    }).then((result) {
      print('');
      checkPoint = "Set-up Random Keys";
      return setupRandKeys();
    }).then((result) {
      print('');
      checkPoint = "Process Updates";
      return processUpdates(maxIters, div);
    }).then((result) {
      String totValue = formatAmount(grandTotAmt, true, 2);
      print("Grand Total added to database = \$${totValue}");
      return db.close();
      // exit(0); pretty much never call exit()
    });
  }).catchError((error) => fatal(checkPoint, error));
}

Edit: Oops, looking more closely I got bit by the scoping problem... I added a level of nesting just to capture the needed vars in a scope accessible by the following blocks. I'm also removing the hungarian-ish notation, because... don't do that in Dart :)

Upvotes: 1

Related Questions