The built-in Future class lets us create an object that runs some code after a background job is finished.

If you are coming from a JavaScript background then you might know what a Promise is. Dart has a similar Future class which is used to create objects that executes a piece of code later in the future. What’s the heck that means?
Well, Dart is a single-threaded language but support asynchronous programming, that means some of the jobs like fetching data from the remote server can be done in the background without blocking the main thread. Once background job is finished, it can notify the main thread with some data it might have generated like network response.
The built-in Future class lets us create an object that runs some code after a background job is finished. There are many libraries that return objects of Future types like http.get function that retrieves remote server data.
Create a Future object
To create a Future object, we need to instantiate Future class by passing an invoker function (usually an anonymous function or fat arrow function) whose job is to return a value or another Future object that returns a value.
A Future object has then and catchError methods which are used to register callback functions. catchError method is called when an error or exception is thrown in the invoker function or in the invoker function of the returned Future object (Future object rejected). Else, then block is called when invoker function returns a value or a Future object (Future object resolved).
I am going to use word exception to denote both an error or an exception in this topic, just to make communication easier.

From the above example, we can see that then method of the Future object future is called when Future object is resolved successfully.
💡 If you are not sure about the Data Type of return value in the invoker function, use dynamic Data Type like Future<dynamic> future = Future(invoker);.
We can also see that print statement 3 was called before print statement 2. This is because, then and catchError callbacks do not block the program until the return value from the invoker function is ready.
After Dart hits then or catchError statement, functions passed to these methods are executed later in the future (once Future object is resolved or rejected).
💡 Even though invoker function in the above exmple returns a String value immediately, it still will be processed asynchronously.
As explained earlier, Future invoker function can also return another Future object. In that case, the parent Future object is resolved when the child Future object is resolved. If child Future object is rejected, parent Future object is also rejected. This is explained in the below example.

From the above example, we can see that both success and error callback can be registered using then method only. However, error callback must be given with onError key because it is an optional named parameter.
A real-world example would be to return something in the invoker function after a few seconds. Future class provides named constructor Future.delayed which executes the invoker function after a delay.

If you run the above example, you would see print statements 1 and 2 executing immediately while statement 3 is executed after 3 seconds.
The amazing this is about then and catchError methods are that they return a Future object. Think then and catchError methods like Future class constructor which accepts an invoker function and returns a value.
Hence, another then method can be chained on then method or a catchError method. Same goes for catchError method. But the usual practice is to chain then methods but keep a single catchError method at the end which catches error occurred in the Future object or any of the then methods. This keeps our code clean and easy to understand.

As we can see from the above example, catchError method is called as soon as an exception was thrown in a then callback. If we had then method chained to catchError, it would have executed (as long as no exceptions are thrown in the catchError) as catchError also returns a Future object.
async/await
Truth to be told, then chaining and catching errors in catchError does sound little intimidating. We are so used to writing synchronous code, we want statements to be executed one after another in a linear fashion. async and await keywords solve makes that happen.
💡 If you are familiar with JavaScript promises and async/await features of ES6, this topic will be a piece of cake for you to understand. If you want to learn about these features of JavaScript, I have an article just for that.
await keyword when place before a Future object like await future;, blocks the execution of further statements until the future object is resolved or rejected. Any function that implements await should be labelled with async keyword and that makes the function to return a Future object by default.
Any await expression returns a return value of the future object, hence it can be stored in a variable. If future object throws an exception, it should be caught with try/catch block.

In the above example, we have used http package which is not provided by Dart standard library. This package needs to be installed by pub. We will discuss more the package management in later topics but if you want to run the above example, follow this package installation guide.
If you are thinking that Future object creates a new thread to execute code in the invoker function, then you are wrong but I don’t blame you. Read this Medium article which explains this concept.
💡 I am not sure about this in Dart (drop a comment if you know), but async/await is syntactical sugar for then and catch method like in the JavaScript, read here.