preface

Most of the front-end development frameworks we are familiar with are event-driven. Event-driven means that you must have event loops and event queues in your program. An event loop constantly retrieves and processes events from the event queue. This means that your program must support asynchrony.

In Android the structure is Looper/Handler; In iOS, RunLoop; In JavaScript, it’s an Event Loop.

Likewise, the Flutter/Dart is event-driven and has its own Event Loop. And this Event Loop is very similar to JavaScript, very similar. Dart is supposed to replace JS, after all. Let’s take a look at the Event Loop in Dart.

The Dart’s Event Loop

The Dart event loop is shown below. It’s basically the same as JavaScript. There are two queues in the loop. One is the MicroTask queue and the other is the Event queue.

  • Event queues contain external events, such as I/O,Timer, draw events, and so on.
  • The microtask queue contains microtasks within the Dart, primarily throughscheduleMicrotaskTo scheduling.

Dart’s event loop runs according to the following rules:

  • First, all the microtasks in the microtask queue are processed.
  • After all the microtasks are done. Fetch an event from the event queue.
  • Return to the microtask queue and continue the loop.

Notice everything in step 1, which means Dart has to process all the microtasks before processing the event queue. If at any point there are eight microtasks in the microtask queue and two events in the event queue, Dart will first process all eight microtasks and then remove one event from the event queue, and then return to the microtask queue to see if there are any outstanding microtasks.

In short, the microtask queue is processed all at once and the event queue is processed one at a time.

This process needs to be clear to understand the order in which the Dart code is executed.

Asynchronous execution

So how do you make your code execute asynchronously in Dart? It is simple to place code to be executed asynchronously in a microtask queue or event queue.

  • You can callscheduleMicrotaskTo get the code toMicro tasksIs executed asynchronously
    scheduleMicrotask((){
        print('a microtask');
    });
Copy the code
  • You can callTimer.runTo get the code toEventIs executed asynchronously
   Timer.run((){
       print('a event');
   });
Copy the code

Ok, now you know how to make your Dart code execute asynchronously. It doesn’t seem too complicated, but you need to be clear about the order in which your asynchronous code executes. This is one of the most common front-end interview questions. To take a simple example, does the following code say “executed”?

main() {
     Timer.run(() { print("executed"); });  
      foo() {
        scheduleMicrotask(foo);  
      }
      foo();
    }
Copy the code

The answer is no, because there will always be a foo in the microtask queue. The Event Loop has no chance to process the Event queue. There are more complex examples where a lot of asynchronous code is mixed and nested and asked what the order of execution is, which requires careful analysis according to the Event Loop rule above.

Just like JS, it’s easy to fall into “Callback hell” using only Callback functions for asynchracy. To avoid this problem, JS introduced promises. Similarly, Dart introduces Future.

Future

To use a Future, dart. Async is introduced

import 'dart:async';
Copy the code

The Future provides you with a list of constructors to choose from.

Create a Future that runs immediately in the event queue:

Future(() => print('Future running in Event Queue now'));
Copy the code

Create a Future that runs in the event queue with a delay of 1 second:

Future.delayed(const Duration(seconds:1), () = >print('Future running in Event Queue in 1 second'));
Copy the code

Create a Future to run on the microtask queue:

Future.microtask(() => print('Future running in Microtask Queue'));
Copy the code

Create a Future that runs synchronously:

Future.sync(() = >print('Synchronous running Future'));
Copy the code

Yeah, you read that right. It’s synchronized.

Note here that the synchronous run refers to the fact that the Future is constructed with a function that is passed in synchronously, and the Future’s then callback is scheduled to be executed asynchronously on the microtask queue.

With a Future, you solve the “callback hell” problem by calling then to string together callback functions.

Future(()=> print('task'))
    .then((_)=> print('callback1'))
    .then((_)=> print('callback2'));
Copy the code

After the task is printed, the callback functions strung together by THEN are executed in the order of the link. What if task execution fails? You can chain an error handler via catchError:

 Future(()=> throw 'we have a problem')
      .then((_)=> print('callback1'))
      .then((_)=> print('callback2'))
      .catchError((error)=>print('$error'));
Copy the code

The above Future executes directly with an exception that will be caught by catchError. A block of catch code similar to the Try /catch mechanism in Java. Only the code in catchError will be executed. None of the code in either THEN is executed.

Now that we have a Java-like try/catch, we should also have a Finally in Java. Yes, that’s whenComplete:

Future(()=> throw 'we have a problem')
    .then((_)=> print('callback1'))
    .then((_)=> print('callback2'))
    .catchError((error)=>print('$error'))
    .whenComplete(()=> print('whenComplete'));
Copy the code

WhenComplete must be executed whether the Future completes normally or throws an exception.

So that’s an introduction to some of the main uses of Future. The implementation mechanism behind Future is a bit complicated. Here are a few interesting hints about the Future from the Dart website. Let’s get a feel for it:

  1. Those callbacks that you string together through then are inFutureThey are executed immediately upon completion, meaning that they are executed synchronously rather than being scheduled to be executed asynchronously.
  2. ifFutureIn the callthenThis is done before you string the callback function,

    These callbacks are then scheduled to be executed asynchronously on the microtask queue.
  3. throughFuture()andFuture.delayed()instantiatedFutureInstead of executing synchronously, they are scheduled to be executed asynchronously on the event queue.
  4. throughFuture.value()instantiatedFutureIt is scheduled to be completed asynchronously on the microtask queue, similar to item 2.
  5. throughFuture.sync()instantiatedFutureExecutes its input function synchronously, and then (unless the input function returns oneFutureDispatch to the microtask queue to complete itself, similar to article 2.

It follows from the above that at least some of the code in the Future will be executed asynchronously, either by its input functions and callbacks, or by its callbacks alone.

I don’t know if you’ve noticed, but you don’t actually have control over the Future objects that are generated by those Future constructors. When it’s done will have to wait for the system to schedule it. You can only passively wait for the Future to complete and then call the callback you set. What if you want to manually control a Future? Use Completer.

Completer

Here’s an example of a Completer

Instantiate a Completer
var completer = Completer();
// Here you can get the Future inside the completer
var future = completer.future;
// string the callback function if necessary.
future.then((value)=> print('$value'));

// Do something else.// Set it to done
completer.complete("done");

Copy the code

In the code snippet above, when you create a Completer, it contains a Future inside. You can use the then, catchError, and whenComplete strings on this Future to attach the callbacks you need. Take the Completer instance, and call the Completer’s Future at the right place in your code. Control is entirely in your own code. You can also end the Future with an exception by calling completeError.

The summary is:

  • I created, finished calling my callback on the line: useFuture.
  • I created it, I have to finish it: withCompleter.

Futures mitigate the problem of callback hell as opposed to scheduling callback functions. But if the Future has a lot of things to string together, the code will still be less readable. Especially various Future nested, is quite brain-burning.

So how about a little bit more force? Can!!!! JavaScript has async/await, Dart has as well.

async/await

What are async and await? They are keywords in the Dart language that allow you to write asynchronous code as synchronous code. What does that mean? Look at this example:

foo() async {
  print('foo E');
  String value = await bar();
  print('foo X $value');
}

bar() async {
  print("bar E");
  return "hello";
}

main() {
  print('main E');
  foo();
  print("main X");
}
Copy the code

The function foo is decorated with the keyword async and has three lines of code inside it that look just like a normal function. But there is an await keyword to the right of the equals sign on line 2, and the presence of await splits code that looks like it might be executed synchronously into two parts. As shown below:

foo
await
Future
then
Future

After the above code is run, the terminal will output the following:

print('foo X $value')
main

The foo function in the code above can be implemented as a Future as follows, which is equivalent

foo() {
  print('foo E');
  return Future.sync(bar).then((value) => print('foo X $value'));
}
Copy the code

“Await” is not like the literal sense that an application runs here and then stops doing nothing and waits for the Future to complete. Instead, it immediately ends execution of the current function and returns a Future. The rest of the code in the function is executed asynchronously by scheduling.

  • awaitOnly in theasyncOccurs in the function.
  • asyncMore than one can occur in a functionawaitEach time you see one, return oneFuture, the actual result is similar to usingthenString of callbacks.
  • asyncThe function can also be absentawaitAfter the synchronization of the function body is completeFuture.

Another advantage of using async and await is that we can use the same try/catch mechanism as synchronous code to do exception handling.

foo() async {
  try {
    print('foo E');
    var value = await bar();
    print('foo X $value');
  } catch (e) {
    // Exceptions in synchronously executed code and exceptions in asynchronously executed code are caught
  } finally{}}Copy the code

In daily usage scenarios, we usually use async and await to asynchronously process IO, network requests, and time-consuming operations such as Platform channels communication in Flutter.

conclusion

This paper briefly introduces the asynchronous operation mechanism of Flutter/Dart. Starting from the basic of asynchronous operation (Event Loop), this paper first introduces the original asynchronous operation mechanism of Flutter/Dart, which directly schedules callback functions. To the Future; To async and await. Learn how the asynchronous mechanism of Flutter/Dart evolved step by step. For students who have been engaged in Native development and do not know much about JavaScrip, this asynchronous mechanism is quite different from Native development. They need more hands-on practice and brain thinking to adapt to it. This article does not cover source analysis of Future implementations in Dart: Async or less common classes like Stream. I’ll write another article about that if you want to know about it.