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:

console.log("registering click handler");

button.addEventListener('click', () => {
  console.log("get click");
});
console.log("all done");

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

  setTimeout(function(){ alert("I'm callback!"); }, 3000);

Or custom functions

function calculate(num1, num2, callbackFunction) { //take a callback function
    return callbackFunction(num1, num2);
}

function calcProduct(num1, num2) { // a callback
    return num1 * num2;
}

function calcSum(num1, num2) { // another callback
    return num1 + num2;
}
console.log(calculate(4, 25, calcProduct)); // cli: 100
console.log(calculate(4, 25, calcSum)); // cli : 29

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

Executor and states

Concretely, the constructor syntax for a promise looks like:

let promise = new Promise(function(resolve, reject) { /*the EXECUTOR code here*/});

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.

let promise = new Promise(function(resolve, reject) { // reminds that the executor is automatically executed when the promise is constructed
  setTimeout(() => resolve("done"), 1000); // after 1 second, we consider the operation successful and we return the result "done"
});

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:

let promise = new Promise(function(resolve, reject) { // reminds that the executor is automatically executed when the promise is constructed
  setTimeout(() => reject(new Error("Whoops!")), 1000); // after 1 second, we consider the operation failed and we return an error object
});

A promise which is either resolved or rejected 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:

promise.then(
  function(result) { /* handle a successful result */ },
  function(error) { /* handle an error */ }
);

The first argument of .then is the function to run when the promise is resolved, and takes the result. The second argument is the function to run when the promise is rejected, and receives the error from the executor. It can be used as follow:

let promise = new Promise(function(resolve, reject) { //don't forget ! the code runs automatically, but we wait 1 sec
  setTimeout(() => resolve("done!"), 1000);
});
promise.then(
  result => alert(result), // shows "done!" after 1 second since we always resolve
  error => alert(error) // doesn't run
);

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.

let promise = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("Whoops!")), 1000);
});
// .catch(f) is the same as promise.then(null, f)
promise.catch(alert); // shows "Error: Whoops!" after 1 second

finally

The call to .finally will always run the consuming function associated with when the promise is settled (i.e. has been resolved or rejected). 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.

new Promise((resolve, reject) => {
  setTimeout(() => resolve("result"), 2000)
})
  .finally(() => alert("Promise ready. Do global task"))
  .then(result => alert(result)); // .then still handles the result. IDEM for reject

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.

new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000); // asyncrhonous

}).then(function(result) { // when the above executor finishes, execute this one

  alert(result); // 1
  return result * 2;

}).then(function(result) { // handle the return value of the previous executor (result * 2)

  alert(result); // 2
  return result * 3;

}).then(function(result) { // handle the return value of the previous executor (result * 3)

  alert(result); // 4
  return result * 4;

});

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.

new Promise(function(resolve, reject) {

  setTimeout(() => resolve(1), 1000);

}).then(function(result) { // triggered after 1sec

  alert(result); // 1

  return new Promise((resolve, reject) => { // returning a new promise. REMEMBER! a new promise constructor automatically run the executor!
    setTimeout(() => resolve(result * 2), 5000);
  });

}).then(function(result) { // triggered after 5 sec (so ~6sec in total since the begigning)

  alert(result); // 2

  return new Promise((resolve, reject) => { //Idem
    setTimeout(() => reject(result * 3), 2000); //this time we trigger an error for the sake of example
  });

}).then(null, function(error) { // or (error) => alert(error)

  alert(error); // 6, in the error state
});

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.

fetch('https://api.github.com/repos/ceri-num/uv-cdaw/commits')
  .then(response => response.json())
  .then(commits => fetch("https://api.github.com/users/"+commits[10].author.login))
  .then(response => response.json())
  .then(githubUser => {
    let img = document.createElement('img');
    img.src = githubUser.avatar_url;
    img.className = "promise-avatar-example";
    document.body.append(img);

    setTimeout(() => img.remove(), 3000); // async callback, removing the img
  });

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.

async function f() {
  return 7;
}

f().then(console.log); // cli : 7

Is equivalent to

function f(){
  return new Promise((resolve, reject) => resolve(7));
}

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 functions. It makes JavaScript actually wait that the promise's state settles and return its result.

The syntax looks like:

let value = await promise; // works only inside async functions

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.

async function f() {
  let promise = new Promise((resolve, reject) => { // our previous example with the executor waiting 1 sec
    setTimeout(() => resolve("done!"), 1000)
  });
  // here the clock is already running
  let result = await promise; // BLOCK! Wait until the promise resolves. Result store the result of the promise, here "done"

  alert(result); // trigger an alert after waiting the promise to end, ~1 sec. after
}
f();

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.

async function myGitHubAvatar(){
  let response = await fetch('https://api.github.com/repos/ceri-num/uv-cdaw/commits');
  let commits = await response.json();
  let user = await fetch("https://api.github.com/users/"+commits[10].author.login);
  let githubUser = await user.json();
 	
  let img = document.createElement('img');
  img.src = githubUser.avatar_url;
  img.className = "promise-avatar-example";
  document.body.append(img);

  await new Promise((resolve, reject) => setTimeout(resolve, 3000)); // async callback, removing the img
  
  img.remove();
  return githubUser
}
myGitHubAvatar();

To conclude, to call an async function from an non-async, do not forget that you receive a promise!

async function waitingF() {
  await new Promise(resolve => setTimeout(resolve, 1000)); //wait 1 sec
  return 7;
}
function f() { //non async function, we can't use await here!
  waitingF().then(result => alert(result)); //simply use .then
}
f();

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:

  1. Send a POST request to the server, indicating that the state of the app (e.g. the game) has changed

  2. Update, in the server, the state of the app with the newly received state from the client (remember to perform security tests!)

  3. 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?