arrow-left

All pages
gitbookPowered by GitBook
1 of 1

Loading...

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.

circle-info

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.

triangle-exclamation

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:

hashtag
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.

circle-check

We have already seen callbacks a lot in the 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).

triangle-exclamation

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.

hashtag
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

hashtag
Executor and states

Concretely, the constructor syntax for a promise looks like:

circle-info

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:

circle-info

A promise which is either resolved or rejected is called settled.

triangle-exclamation

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.

circle-exclamation

resolve/reject expect only one argument (or none) and will ignore additional arguments.

hashtag
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

hashtag
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 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:

circle-info

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.

hashtag
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.

hashtag
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.).

circle-exclamation

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

circle-info

.finally does not process any promise: it passes through results and errors to the next handler.

hashtag
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 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 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.

circle-info

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!

hashtag
Micro-managing the synchronicity

hashtag
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.

hashtag
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:

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.

circle-info

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!

circle-exclamation

Handling error for async/await function is relying on try...catch syntax, instead of the .catch handler.

hashtag
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.

circle-check

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. is an interesting post and reflexion about this topic in JavaScript, wrote by Gary Weiss.

circle-info

An alternative is to use websocket. This way, the server can dynamically send information to the clients who had open a communication with it.

They avoid inversion controlarrow-up-right
Events
thenablearrow-up-right
fetcharrow-up-right
Herearrow-up-right
Illustration on how the executor moves promise into one of these two states
Illustration of chaining
console.log("registering click handler");

button.addEventListener('click', () => {
  console.log("get click");
});
console.log("all done");
  setTimeout(function(){ alert("I'm callback!"); }, 3000);
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
let promise = new Promise(function(resolve, reject) { /*the EXECUTOR code here*/});
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"
});
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
});
promise.then(
  function(result) { /* handle a successful result */ },
  function(error) { /* handle an error */ }
);
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
);
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
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
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;

});
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
});
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
  });
async function f() {
  return 7;
}

f().then(console.log); // cli : 7
function f(){
  return new Promise((resolve, reject) => resolve(7));
}
let value = await promise; // works only inside async functions
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();
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();
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();