Celestine is currently taking an apprenticeship at Peerigon to become a professional web developer. As part of her weekly routine, she publishes a blog post each week about what she has been doing and what she has learned.
Problem to solve
JavaScript can only do one task at once. Therefore, code would be blocked from being executed while e.g. waiting for a server to respond. There are different approaches to solve these problems.
Solution
There are four solutions I want to introduce: callbacks, promises, generators and async/await.
Callbacks
They are the easiest way to write async code, but lead to a so called callback hell. Which means the code is hard to read because the functions are nested inside each other. The callback function is usually executed later. It’s a way to communicate back to the caller that something has been finished. An example for a function with a callback and proper error handling is given below. Notice that by convention, the callback should always be the last argument.
function fetchBlogPosts(url, (error, blogPost) => {
if (error) {
alert('Oh, no!');
return;
}
render(blogPost);
});
Promises
Promises are there to save you from callback hell. They turn something like this:
someAsyncFunction((error, result) => {
someAsyncFunction((error, result) => {
someAsyncFunction((error, result) => {
});
});
});
into that:
someAsyncFunction()
.then(someAsyncFunction)
.then(someAsyncFunction)
.then(someAsyncFunction)
Which is way more readable.
Promises are created with an executor
function which accepts a resolve
and a reject
callback: const p1 = new Promise((resolve, reject) => {});
. The executor
is called synchronously.
Promises are settled in the end, which means they are either fulfilled (success case) or rejected (error case). To handle a settled promise, there are these functions:
then(onFulfilled, onRejected)
: can have two listeners, whereonRejected
is optional.catch(onRejected)
: just listens for the error casefinally()
: is executed when the promise is settled, no matter if fulfilled or rejected.
For further reading, please refer to Promises.
Generators
A generator is a special iterator created with function* generateSomething(){}
. This function does not actually execute when it's called, but returns a generator instance which provides the following methods:
next(someValue)
: PassessomeValue
to the generator and executes the function till the nextyield
orreturn
. It then returns an object with two properties,done
andvalue
, wheredone
marks if the generator is finished.return()
: immediately finishes the generator.throw()
: throws an exception to the generator.
A generator can be used to create pausable functions. Everytime the generator hits a yield
or return
, it stops. Later, it can be resumed using the next()
method. This makes them suited for asynchronous tasks that might take longer.
Async/Await
This is the new option to write asynchronous code in a synchronous way since ES7. The keyword async
in front of a function declaration marks it as asynchronous. Inside this function you can wait for a returned promise by using await
. Furthermore, it always returns a promise, even without using await
. It works similar like a generator and under the hood, it uses the same foundation: a pausable function.
What I learned
- There are always multiple solutions for one problem, some are easier than others.