Async and await are actually the two keywords Dart asynchronous programming uses to simplify operations on the asynchronous API. Its purpose is to enable asynchronous code to be implemented using synchronous code structures. If you’ve read the previous Future and Stream articles, you’ll know that asynchronous callbacks are used for final returns or exceptions. ** However, the purpose of async-await is to simplify these asynchronous callback modes. By simplifying the syntax, the original asynchronous callback mode is written into simple synchronous mode structure. Note that using the await keyword must be used in conjunction with the async keyword to work. In essence async-await is equivalent to another encapsulation of future-related API interface, providing a more convenient way to operate future-related API.

1. Why async-await

As we learned from the asynchronous programming Future, Future generally handles data callbacks and exception callbacks using THEN and catchError. This is actually a way based on asynchronous callback, if the asynchronous operation dependencies is more complex to callback code is multifarious, in order to simplify the steps async – await keywords by synchronizing the code structure to implement the asynchronous operation, so as to make the code more concise and readable, moreover in exception handling way will be more simple.

1.1 Comparison of implementation codes

  • Future implementation
void main() {
  _loadUserFromSQL().then((userInfo) {
    return _fetchSessionToken(userInfo);
  }).then((token) {
    return _fetchData(token);
  }).then((data){
    print('$data');
  });
  print('main is executed! ');
}

class UserInfo {
  final String userName;
  final String pwd;
  bool isValid;

  UserInfo(this.userName, this.pwd);
}

// Read user information from local SQL
Future<UserInfo> _loadUserFromSQL() {
  return Future.delayed(
      Duration(seconds: 2), () => UserInfo('gitchat'.'123456'));
}

// Obtain the user token
Future<String> _fetchSessionToken(UserInfo userInfo) {
  return Future.delayed(Duration(seconds: 2), '3424324sfdsfsdf24324234');
}

// Request data
Future<String> _fetchData(String token) {
  return Future.delayed(
      Duration(seconds: 2),
      () => token.isNotEmpty
          ? Future.value('this is data')
          : Future.error('this is error'));
}
Copy the code

Output result:

  • Async-await implementation
void main() async {// Note: async needs to be added because await must be valid inside async method
  var userInfo = await _loadUserFromSQL();
  var token = await _fetchSessionToken(userInfo);
  var data = await _fetchData(token);
  print('$data');
  print('main is executed! ');
}

class UserInfo {
  final String userName;
  final String pwd;
  bool isValid;

  UserInfo(this.userName, this.pwd);
}

// Read user information from local SQL
Future<UserInfo> _loadUserFromSQL() {
  return Future.delayed(
      Duration(seconds: 2), () => UserInfo('gitchat'.'123456'));
}

// Obtain the user token
Future<String> _fetchSessionToken(UserInfo userInfo) {
  return Future.delayed(Duration(seconds: 2), () = >'3424324sfdsfsdf24324234');
}

// Request data
Future<String> _fetchData(String token) {
  return Future.delayed(
      Duration(seconds: 2),
      () => token.isNotEmpty
          ? Future.value('this is data')
          : Future.error('this is error'));
}
Copy the code

Output result:

1.2 Compare the handling of exceptions

  • The realization of the Future
void main() {
  _loadUserFromSQL().then((userInfo) {
    return _fetchSessionToken(userInfo);
  }).then((token) {
    return _fetchData(token);
  }).catchError((e) {
    print('fetch data is error: $e');
  }).whenComplete(() => 'all is done');
  print('main is executed! ');
}
Copy the code
  • The realization of the async and await
void main() async {
  // Note: async needs to be added because await must be valid inside async method
  try {
    var userInfo = await _loadUserFromSQL();
    var token = await _fetchSessionToken(userInfo);
    var data = await _fetchData(token);
    print('$data');
  } on Exception catch (e) {
    print('this is error: $e');
  } finally {
    print('all is done');
  }
  print('main is executed! ');
}
Copy the code

By comparison, it is found that future. then calls the link more clearly than async-await, and calls based on asynchronous callback are clearer. And in the code implementation and synchronous structure analysis async-await appears to be more simple and handle exceptions more aspect, more in line with the logic of synchronous code.

2. What is async-await

2.1 Basic introduction of async-await

Async-await is essentially a simplified form of the Future API, writing asynchronous callback code as a synchronous code structure. The async keyword always returns a Future object, so async does not block the current thread. The Future will eventually be added to the EventQueue. The EventQueue execution does not check and process the Microtask Queue and EventQueue until the main function has finished executing. The await keyword means to interrupt the current code execution process until the current async method has finished executing, otherwise the code below will be in a waiting state. But there are two rules to follow:

  • To define an asynchronous function, add the async keyword before the function body
  • The **await keyword is valid only in functions decorated with the async keyword

六四屠杀

void main() {
  print("executedFuture return ${executedFuture() is Future}");// So print true
  print('main is executed! ');
}

executedFuture() async {// Async returns a Future object by default
  print('executedFuture start');
  await Future.delayed(Duration(seconds: 1), () = >print('future is finished'));//await the Future to complete
  print('Future is executed end');// The executedFuture End output must wait for the await Future to finish before executing
}
Copy the code

Output result:Analyzing the output, first the main function synchronizes the executedFuture and print functions, so the output will be synchronized immediately“ExecutedFuture start”But since executedFuture is an async function, await a Future, within the scope of executedFuture, and therefore execute after await, wait for Future data to arrive before executing the following statement. But the executedFuture is executed and the ** “main is executed!” in the main function is executed.After the main execution is complete, it checks the MicroTask Queue to see if there are any microtasks that need to be executed. If not, the Event Queue is checked to see if there are any events that need to be processed (where a Future is also an Event), so the Future in executedFuture is checked and passed to EventLoop for processing“The future is finished”, and finally execute the output“Future is executed end”. ** ** If the above example is modified:

void main() async {// Main becomes an async function
  await executedFuture(); //executedFuture adds await keyword
  print('main is executed! ');
}

executedFuture() async {
  print('executedFuture start');
  await Future.delayed(Duration(seconds: 1), () = >print('future is finished'));
  print('Future is executed end');// The executedFuture End output must wait for the await Future to finish before executing
}
Copy the code

Output result:Parsing the output, you may be wondering why “main is executed” is executed at the end, but it’s easy to understand that main is an async function and must waitawait executedFuture()The following will be executed only after the execution is completeprint('main is executed! '). If the executedFuture is not finished, the code behind the main function must wait, so you do not directly add async to the whole main function, which forces the whole main function to execute synchronously.

2.2 Understanding async-await with EventLoop

We know from the above that the async-await nature is actually a Future, and the nature is to add an Event to the Event Queue and then an EventLoop to process it. But they’re completely different in the form of code structure how does it do that? Take a look. To give you an example:

  • The understanding of the Future. Then
class DataWrapper {
  final String data;

  DataWrapper(this.data);
}

Future<DataWrapper> createData() {
  return _loadDataFromDisk().then((id) {
    return _requestNetworkData(id);
  }).then((data) {
    return DataWrapper(data);
  });
}

Future<String> _loadDataFromDisk() {
  return Future.delayed(Duration(seconds: 2), () = >'1001');
}

Future<String> _requestNetworkData(String id) {
  return Future.delayed(
      Duration(seconds: 2), () = >'this is id:$id data from network');
}
Copy the code

In fact, through the Future chain call execution logic can becreateDataThe createData function is divided into 1, 2, and 3:So the first block is whencreateDataWhen a function is called, the call is executed synchronously_loadDataFromDiskFunction, which returns a Future object and then waits for the data to arrive in the EventLoop_loadDataFromDiskThe function is done.Then, when_loadDataFromDiskWhen data from the Future function arrives, it fires in block 2thenThe function callback is executed as soon as the ID is given_requestNetworkDataThe function makes an HTTP request, returns a Future object, and then waits for the HTTP data to arrive in the EventLoop and be processed_requestNetworkDataThe function ends.Finally, when_requestNetworkDataWhen the data for the Future function comes in, the secondthenThe function is triggered by the callback and then createdDataWrapperReturn the final data object. The Future returned by the entire createData function will eventually return real data. If an external caller calls the function, the Future will return real data in itsthenYou get the final one in the delta functionDataWrapper

  • The understanding of async and await
class DataWrapper {
  final String data;

  DataWrapper(this.data);
}

Future<DataWrapper> createData() async {//createData adds the async keyword to indicate that this is an asynchronous function
  var id = await _loadDataFromDisk(); //await execution _loadDataFromDisk gets the ID from the disk
  var data = await _requestNetworkData(id); _requestNetworkData returns data by passing in the ID of _loadDataFromDisk
  return DataWrapper(data); // Finally return the DataWrapper object
}

Future<String> _loadDataFromDisk() {
  return Future.delayed(Duration(seconds: 2), () = >'1001');
}

Future<String> _requestNetworkData(String id) {
  return Future.delayed(
      Duration(seconds: 2), () = >'this is id:$id data from network');
}
Copy the code

It is also possible to perform sequential logic with similar synchronous calls to async-awaitcreateDataThe code is broken down by event into the following form, but previously passedthenThe createData function is divided into 1, 2, and 3:First, when createData starts executing, the first wait is triggered, and createData returns its Future object to the calling function. Note:CreateData will return its Future object to the calling function when the first await is awaited and _loadDataFromDisk is called, and createData will have finished executingIf you don’t explicitly see a Future object returned, this is because async keyword syntax sugar does it for you. Some people will wonder if the following step 3 is not returned? Please note: the following returns areDataWrapperObject is not oneFuture<DataWrapper>“, so when we execute createData and get the first await, we will immediately return a Future object and createData will finish executing. Trigger the first wait and execute_loadDataFromDiskFunction, which returns a Future object and waits for the dataidTo come.Then, block 2 is executed when_loadDataFromDiskFunction, which returns a Future objectidWhen the data arrives, a second await is triggered to wait and called_requestNetworkDataFunction that makes an HTTP network request and returns a Future, then waits for the HTTP Future to arrive with await.In the end,_requestNetworkDataThe function returns the data in the Future, gets the HTTP data, and finally returns a DataWrapper, so the entire createData Future gets the final DataWrapper.

3. How to use async-await

3.1 Basic Usage

We can better understand the usage of async-await by comparing the general synchronous implementation, the asynchronous future. then implementation, and the asynchronous async-await implementation. Using async-await is actually equivalent to writing it as a synchronous code structure. Take the above example as an example

  • Synchronous implementation

Assuming that both the _loadDataFromDisk and _requestNetworkData functions are executed synchronously, it is easy to write their execution code:

DataWrapper createData() {
  var id = _loadDataFromDisk();// Synchronous execution direct return ID
  var data = _requestNetworkData(id);// Execute directly return data synchronously
  return DataWrapper(data);
}
Copy the code
  • Asynchronous future. then implementation
Future<DataWrapper> createData() {// Since the execution is asynchronous, note that the object returned is a Future
  return _loadDataFromDisk().then((id) {// Get the ID in the asynchronous callback then function
    return _requestNetworkData(id);
  }).then((data) {// We need to get data in the asynchronous callback then function
    return DataWrapper(data);// Finally return the final DataWrapper
  });
}
Copy the code
  • Async -await implementation

The overall code structure is exactly the same as the synchronous code structure except that the async-await keyword is added and a Future object is returned

Future<DataWrapper> createData() async {//createData adds the async keyword to indicate that this is an asynchronous function
  // Note that createData completes the createData function by returning a Future
      
        object when it encounters the first await _loadDataFromDisk.
      
  var id = await _loadDataFromDisk(); //await execution _loadDataFromDisk gets the ID from the disk
  var data = await _requestNetworkData(id); _requestNetworkData returns data by passing in the ID of _loadDataFromDisk
  return DataWrapper(data); // Finally return the DataWrapper object
}
Copy the code

3.2 Exception Handling

  • Synchronous implementation

Synchronous execution of code for exception handling can only be try-catch-finally.

DataWrapper createData() {
  try {
    var id = _loadDataFromDisk();
    var data = _requestNetworkData(id);
    return DataWrapper(data);
  } on Exception catch (e) {
    print('this is error');
  } finally {
    print('executed done'); }}Copy the code
  • Asynchronous future. then implementation

Future.then implementation of asynchronous exception catching is generally implemented with catchError.

Future<DataWrapper> createData() {
  return _loadDataFromDisk().then((id) {
    return _requestNetworkData(id);
  }).then((data) {
    return DataWrapper(data);
  }).catchError((e) {//catchError
    print('this is error: $e');
  }).whenComplete((){
    print('executed is done');
  });
}
Copy the code
  • Async -await implementation

The async-await implementation catches asynchronous exceptions exactly as the synchronous implementation does.

Future<DataWrapper> createData() async {
  try {
    var id = await _loadDataFromDisk();
    var data = await _requestNetworkData(id);
    return DataWrapper(data);
  } on Exception catch (e) {
    print('this is error: $e');
  } finally {
    print('executed is done'); }}Copy the code

4. Async-await usage scenario

In fact, the usage scenarios of async-await can be summarized and analyzed basically after learning the above contents.

  • Most of the scenarios used by Future can be replaced with async-await, and it is recommended to use async-await as well, since these two keywords are intended to simplify Future API calls and to write asynchronous code as synchronous code. It also improves code simplicity.
  • In fact, it can be clearly found from the above example that for some Future with obvious dependencies, it is recommended to use the Future. After all, the chain call function is very powerful, and you can see the dependency relationship between each Future at a glance. Although the callback form is simplified by async-await, the dependency between futures is reduced to some extent.
  • We can use async-await for futures with non-obvious dependencies and independent of each other.

5. Summary from Mr. Xiong Meow

The use of async-await in Dart asynchronous programming ends here. In fact, async-await is the use of syntactic sugar, which is essentially the use of the Future API. The benefit and advantage of async execution is to write synchronous code structures. We also split up its code structure and clearly analyzed the principle behind its syntax sugar. This is very helpful to use async-await.

Thank you for your attention, Mr. Xiong Meow is willing to grow up with you on the technical road!