Asynchronicity
What is the big deal with events? It is that they illustrate how your code can run asynchronously! And this is a very important aspect for a client side application. Indeed, without this asynchronous aspect, waiting a button to be clicked will block the entire page, thus making it unbrowsable. More generally, code runs straight along, making instruction after instruction ; however, if your instruction is quite important and cpu-intensive and take some time to finish, the whole experience will appear to be frozen for the user, unresponding even to its clicks.
This behavior is called blocking : an intensive chunk of code runs without returning control to the browser, and therefore the browser appears to be frozen. The reason is that JavaScript is single threaded.
Even if JS is single threaded, this does not mean that it can't be asynchronous!
Asynchronicity allows us to initiate actions at a specific time, and finish them later without worrying to call them again.
Working with an asyncrhonous requires to change the way of how your think about your code and how it is supposed to run. Otherwise, catastrophes are on the way. Thus you need to be fully aware of the code execution order. The example below will console the first and the last log ; the second being block until someone click on a button:
Callbacks
A callback function is a function (generally anonymous) passed into another function as an argument, which is then invoked inside the outer function to complete some kind of routine or action. This is extremelly usefull to make versatile function by attaching it a specific behavior when it has finished to run.
We have already seen callbacks a lot in the Events section!
Callback are not always associated with events though. They can also be used with simple function (native or custom). For example with Timeout
Or custom functions
It is also possible to use callback functions inside other callback functions and make a whole chain of actions triggering automaticaly (e.g. on click of the button, perform an action then draw).
Be carefull of the nesting of callbacks, since it can quickly become out of control if the depth is too important. A way to alievate this problem is to create small standalone function and do not rely on anonymous function.
Promises
Promises have some similarities with callbacks. They are essentially a returned object to which you attach callback functions, rather than having to pass callbacks into a function. However, promises are specifically made for handling async operations, and have many advantages over old-style callbacks:
Chainning multiple async operations together is simple and easier to read (including the passing of the result to the following promise)
They assure consistency in the event queue
Error handling is better
They avoid inversion control
Executor and states
Concretely, the constructor syntax for a promise looks like:
When new Promise
is created, the executor runs automatically! For now, this is not so powerful ; below you will see why this is very important.
A promise involve always two "entities" in your code, and bind them together. The concept is the following, there is the concept of
An
executor
, that does/returns something and generally take time. For instance, retrieving data over the network according to your REST API.A consuming code, that wants the result of the
executor
once it’s ready (not before! Because it is useless).
The executor
's arguments resolve
and reject
are callbacks automatically provided by JavaScript itself (no need to create them). They should always be present in the definition of the executor of your promise. When the executor obtains the result, be it soon or late, doesn’t matter, it should call one of these callbacks:
resolve(value)
: if the job finished successfully, with result value.reject(error)
: if an error occurred, error is the error object.
(Please mind the three differents states of a promise, as well as the value of the result
property) Now, let's illustrate how this works with a simple executor
code using setTimeout
.
After one second of “processing” the executor
calls resolve("done")
to produce the result. This changes the state of the promise object from pending
to fulfilled
.
This goes the same for the executor rejecting the promise with an error:
A promise which is either resolve
d or reject
ed is called settled.
There can be only a single result or an error. The executor should call only one resolve or one reject. Subsequent call to resolve
or reject
are simply ignored : any state change is final. HOWEVER, instructions after the call to resolve
or reject
are still performed.
resolve
/reject
expect only one argument (or none) and will ignore additional arguments.
Consumming promise
As stated before, a promise bind an executor
with a "consumer" which will receive the result or the error from the executor. A "consumer" is represented by consuming functions that are subscribed to the promise using one the three following methods/handlers:
.then
.catch
.finally
then
The .then
handler is the fundamental one for promises. It dispatches the executor's result
variable to the appropriate consuming function according to the executor's state
. The syntax is:
The first argument of .then
is the function to run when the promise is resolve
d, and takes the result
. The second argument is the function to run when the promise is reject
ed, and receives the error
from the executor. It can be used as follow:
In case you don't ever handle error in your promise, you can ommit the second parameter like and simplify the writting like : promise.then(alert);
because alert only take one argument.
catch
The .catch
handler is designed to simplify the retrieve of the error if you are only interested by it. Instead of using .then
with a null
value for the resolve
like .then(null, errorHandlingFunction)
, we can use .catch(errorHandlingFunction)
which does exactly the same.
finally
The call to .finally
will always run the consuming function associated with when the promise is settled (i.e. has been resolve
d or reject
ed). It is a good handler for performing cleanup (e.g. stopping our loading animations, etc.).
.finally
has no arguments: we do not know whether the promise is successful or not. This should not bother you because the kind of operations here are supposed to be "global", like stoping loading animation, etc.
.finally
does not process any promise: it passes through results
and errors
to the next handler.
Chaining promise
Promises offer a really convenient way to deal with a sequence of asynchronous tasks to be performed one after another, which is call chaining. The idea beyond chaining is that the result is passed through the chain of .then
handlers.
The chaining process is possible because a call to promise.then
returns a thenable object (quite equivalent to a promise). Therefore, we can also call the next .then
on it.
The triggering of the chaining is asynchrone, but even if the two last executors looks like they run in a synchronous way, they don't. They indeed wait the previous executor to finish, but if you are writing code below a .then
handler the code will run probably before the handler.
You can also reintroduce asynchronicity in the chaining by creating and returning a new promise.
Let's see another, more concrete, example with the fetch
method (more powerfull and flexible than XMLHttpRequest
). We will retrieve my GitHub avatar, by finding my name in the commit history of the CDAW project on github, then display it for 3sec before removing it.
Do not forget that you can split your code into reusable function! For example, one for retrieving the GitHub username.
Remember, anyway, that : Promise handling is always asynchronous! Even if a .then
wait the previous .then
to finish, all the rest of your code is still running!
Micro-managing the synchronicity
async
The async
keyword, put in front of a function declaration, turn the function into an async function
. An async function
is a function that knows how to expect the possibility of the await
keyword being used to invoke asynchronous code. An async function
in fact returns -- automatically or not -- a promise.
Is equivalent to
So, async
ensures that the function always returns a promise, wrapping anything non-promises in a promise.
await
The await
keyword works only inside async function
s. It makes JavaScript actually wait that the promise's state settles and return its result.
The syntax looks like:
The important thing to note is that the execution is "paused" until the promise is settled. That means that the code below the await
instruction will not be executed until the promise is finished.
await
doesn’t cost any CPU resources (unless the executor consumes CPU, ofc), because the JavaScript engine can do other jobs in the meantime: execute other scripts, handle events, etc.
Roughly speaking, it can be seen as a more elegant way to deal with .then
handlers.
To conclude, to call an async function
from an non-async, do not forget that you receive a promise!
Handling error for async
/await
function is relying on try...catch
syntax, instead of the .catch
handler.
Synchronizing clients: an introduction
In a complex web app, where interactions between users exist, you will probably need to synchronize several users together. This occurs mostly when a user performed an action, which change the state of the app, and this change needs to be refleted on other users. In a standard web architecture, the sever cannot take any kind of initiative per se, it waits for client to request information. Therefore, the synchronization of users must be thought of in the client side.
On a client-server architecture, the server is the final authority regarding logic and decisions.
Accordingly, you will need three steps to achieve such a synchronization, relying on asynchronous communication:
Send a
POST
request to the server, indicating that the state of the app (e.g. the game) has changedUpdate, in the server, the state of the app with the newly received state from the client (remember to perform security tests!)
All clients should ask regularly the server if there is any change in the app's state. If so, retrieve the change and reflect it accordingly
setTimeOut
is very usefull to achieve this behavior. However, keep in mind that setTimeOut
, as well as all time related function, are not reliable. Even if you set a specific amount of time t
, the wait will be t+Δt
. Thus, you will potentially face desynchronization issues with such a method. This kind of problem has been talked a lot on the internet. Here is an interesting post and reflexion about this topic in JavaScript, wrote by Gary Weiss.
An alternative is to use websocket. This way, the server can dynamically send information to the clients who had open a communication with it.
Last updated
Was this helpful?