async/await
a brief history of asynchronous programming
Asynchronous programming is an important concept that all students of code should understand. It boils down to this code snippet:
const a = do_something();
do_something_else(a);
Suppose do_something
takes a really long time. What do we do while we wait for it to finish?
In single-threaded programming, we must wait because we only have one context. We must use our only processor to watch and wait for do_something
to finish, store the results in a
before calling do_something_else
.
This is called blocking and it is horrible. In a user interface, long-running processes can be triggered from a number of different points. A “Load More” button can trigger an API call, “Save As…” can trigger a file write. Any of these actions can lock up the whole program, which I’m sure is something you may have observed in poorly written desktop programs.
So suppose we have:
const a = fetch('https://api.areadenialweapons.com/v1/users');
update_ui_with_users(a);
While we’re fetching, our entire UI would lock up because we are occupying our entire brain checking whether or not fetch
has finished. We have no brainpower to update the UI. So if a user interacts with the UI while this is happening, it cannot update and will appear locked.
Obviously, this is not okay so when a bunch of smart folks were designing Javascript, they decided to add in the concept of callbacks.
fetch_callback(
'https://api.areadenialweapons.com/v1/users',
function(a) {
update_ui_with_users(a);
}
);
This is substantially better. Instead of waiting for fetch_callback
to finish, we simply tell it what to do when it has finished. This way, fetch_callback
returns immediately and passes back control so that we don’t need to wait for it. The UI will update just fine and when fetch_callback
is complete, we will update asynchronously.
This was great for product managers, but it also led to what became known as callback hell. With just a few layers of callbacks, things can get very confusing. Callbacks are better than blocking, but they make code harder to read and write.
fetch_callback(
'https://api.areadenialweapons.com/v1/users',
function(data) {
parse_json(data, function(json){
update_ui_with_users(json, function(){...});
});
}
);
Promises became popular in functional programming as a solution to this problem.
fetch_promise('https://api.areadenialweapons.com/v1/users')
.then(parse_json_promise)
.then(update_ui_with_users_promise)
.then(...)
This code looks sequential, like the blocking example, but works asynchronously. It is simply a nicer wrapper around a callback. In fetch_promise
, we create a promise object and kick off a request that will fulfill the promise in a callback.
const p = new Promise();
fetch_callback(
'https://api.areadenialweapons.com/v1/users',
function(data) {
p.fulfill(data);
}
)
return p;
p
is returned immediately unfulfilled. We then register callbacks on it using the .then
method. These callbacks can also return promises. (Javascript ES6 has some magic to flatten these promises, although in functional languages like Scala you have to flatten them by hand.)
However, many people still did not like this syntax so in the latest iteration of Javascript, ES7, people created async/await as a syntactical sugar that results in the same thing as a promise chain.
async f() {
const a = await fetch_promise(...)
const b = await parse_json_promise(a)
const c = await update_ui_with_users_promise(b)
...
}
is the equivalent of
fetch_promise('https://api.areadenialweapons.com/v1/users')
.then(parse_json_promise)
.then(update_ui_with_users_promise)
.then(...)
I personally prefer using promise chains, but you know… different strokes for different folks.
Hope this is helpful in providing everyone with an overview of async programming in React. Keep up the good work!