vikramlc / Javascript

0 stars 0 forks source link

Async Javascript - Promises and Callbacks #17

Open vikramlc opened 4 years ago

vikramlc commented 4 years ago

image

vikramlc commented 4 years ago

image

vikramlc commented 4 years ago

Let's imagine this set up here, we have this code and we have our stack. Now in this code here on the left, you see we're defining two functions, then here I set a timer, once this timer is done, we call the show alert function which is the second function we define here and after the timer, we also call greet here. Now when this code executes, the stack which is part of the Javascript engine as you learned will do certain things. Now also be aware that certain other things will be offloaded to the browser, that the browser kind of gives us a bridge where we can talk to certain browser APIs from inside our Javascript code to for example offload certain work. So let's say this code executes now, now what happens is the two functions are created, that's the first thing which happens, the greet and the show alert function and then the first function that is called is actually the built-in setTimeout function. Now setTimeout does one important thing, it reaches out to the browser because it actually is a browser API made available in Javascript and sets up the ongoing timer there, in the browser so to say, so the browser manages this timer as I mentioned earlier and then in Javascript, this function is done and as you learned, it is really done, it does not block any other code execution. So the timer is still there but that is managed by the browser, Javascript is done for now. So the next thing which actually happens is not that show alert executes, keep in mind that this takes 2 seconds, the timer takes 2 seconds, instead Javascript doesn't wait for this as I explained and it instead moves on to the next line, it moves on to the greet method, so now greet executes, before the timer is completed right away after setTimeout is done and the timer is offloaded to the browser, then greet executes in the greet function if you have a look at the left here, we call console log so that's of course also executed and with that we're basically done with the code on the left. Now at some point, the timer is completed though and let's say that actually happens whilst our greet console log code executes. Of course this code executes very fast, it does not take it long, it's not such a long if loop as we wrote it but still, it takes a couple of milliseconds even if it's just a few and now let's say whilst we're executing console log, the timer finishes. Now we need some way of telling our Javascript code, the Javascript engine so to say, that the show alert function which was register this a callback for the timer should be executed and for this, a message queue is used. This is provided by the browser and it's also linked to Javascript so to say. Now in this message queue, the browser registers any code that should execute once we have time for it, in this case the timer. So here, the show alert function, this callback so to say is register this a to-do task once the Javascript engine has time for it, so now the timer is done and this task is registered. Please note, show alert does not execute at this point, it's just register this a to-do. The only thing which is really executed in Javascript at this point is greet and console log because that's what we're currently doing there. So now with that, let's say in Javascript we're done with that console log executed and therefore greet also is done and call stack is empty again. Now we need to get that message or this show alert to-do in our call stack, we know it's a function in Javascript, it should now be executed and for this, we use the event loop. The event loop, just like the message queue, is built into the browser and most Javascript environments, for example also Node.js have that concept of having such an event loop. It's just important to understand that it's not part of the Javascript engine, it's really part of the host environment of Javascript, so of the thing which uses that Javascript engine. So the event loop is part of the browser and the job of the event loop in the end is to synchronize the call stack in the engine with our waiting messages. So in the end, what the event loop does is it runs basically all the time and it always sees, is the stack empty and do we have pending to-dos, and if the stack is empty, then the event loop executes so to say and it pushes any waiting messages or any to-do functions therefore into the call stack. So the event loop waits until the call stack is empty and once that is the case, it moves our message or the callback function we registered earlier as a function that is now actively executed into our call stack. So the message queue is now empty and now this function runs in our Javascript code. So here show alert runs, this calls t he built-in alert function here as you can see on the left, once this is done, the call stack is empty again. This is what the browser does behind the scenes with the event loop and with our code and also with these callback functions we hand off to the browser APIs and that is a pattern that typically is used for async operations. So if we have a look at this code again, what we do here with add event listener is here, we're handing something off to the browser, we're handling this click listener off to the browser and we tell it that this is the function that should be executed once a click occurs, then we continue in Javascript and we have this long taking task which basically occupies our call stack, it's not part of a function here but it therefore is basically running in an anonymous function you could say, in a big anonymous function that wraps everything if you will. So this now executes there and the call stack in our Javascript engine therefore is not empty. So if we click the button, if we reload whilst we're still doing this for loop, then this click is registered and the browser pushes this function as a to-do onto our message queue but the event loop sees that we still have work to do on the call stack and therefore, it waits to execute this function until the call stack is empty and that only happens once this executed and once we logged this result to the console which is why we see the clicked output only after we see the result, even if we click the button whilst this was still going on, simply because of how the browser handles this. And this is really important knowledge, you need to know what Javascript does behind the scenes here to understand why you write code in a certain way and to understand that for example, this code here executes before this code, even though you register this first because this is an async task and you wait for this to happen but you don't wait for it by blocking your Javascript execution but instead that continues and only some things, like this for loop which you typically don't write like this, you don't have such long running for loops typically, such operations can block your Javascript code, these expected long taking operations or these operations like event listeners where you don't know if and how often they will happen, these are handed off through the browser so that your Javascript code is never blocked.

vikramlc commented 4 years ago

Promises: image

vikramlc commented 4 years ago

Promises in action: The catch block in promises follows a top-down approach. So basically if a catch block is the second block then it catches the error from any of the above then blocks. The rest of the then blocks error will not be caught if there are no catch blocks.

const button = document.querySelector('#click');

const getPosition = (opts) => {
  const promise = new Promise((resolve, reject) => {
    navigator.geolocation.getCurrentPosition(success => {
      resolve(success);
    }, error => {

    }, opts);
  });

  return promise;
}

const setTimer = duration => {
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Done');
    }, duration);
  });

  return promise;
};

function trackUserHandler() {
  let positionData;
  getPosition()
    .then(posData => {
      positionData = posData;
      return setTimer(2000);
    })
    .catch(err => {
      console.log(err);
    })
    .then(data => {
      console.log(data, positionData);
    })
    .catch(err => {
      console.log(err);
    });
  setTimer(1000).then(() => {
    console.log('Timer done!');
  });
  console.log('Getting position...');
}
//   navigator.geolocation.getCurrentPosition(
//     positionData => {
//       setTimer(2000).then(data => {
//         console.log(data, positionData);
//       });
//     },
//     error => {
//       console.log(error);
//     }
//   );
//   setTimer(1000).then(() => {
//     console.log('Timer done!');
//   });
//   console.log('Getting position');
// }

button.addEventListener('click', trackUserHandler);
vikramlc commented 4 years ago

Promise States & "finally" You learned about the different promise states:

PENDING => Promise is doing work, neither then() nor catch() executes at this moment

RESOLVED => Promise is resolved => then() executes

REJECTED => Promise was rejected => catch() executes

When you have another then() block after a catch() or then() block, the promise re-enters PENDING mode (keep in mind: then() and catch() always return a new promise - either not resolving to anything or resolving to what you return inside of then()). Only if there are no more then() blocks left, it enters a new, final mode: SETTLED.

Once SETTLED, you can use a special block - finally() - to do final cleanup work. finally() is reached no matter if you resolved or rejected before.

Here's an example:

somePromiseCreatingCode()
    .then(firstResult => {
        return 'done with first promise';
    })
    .catch(err => {
        // would handle any errors thrown before
        // implicitly returns a new promise - just like then()
    })
    .finally(() => {
        // the promise is settled now - finally() will NOT return a new promise!
        // you can do final cleanup work here
    });
vikramlc commented 4 years ago

Async/Await: without error handling:

async function trackUserHandler() {
  const posData = await getPosition();
  const timerData = await setTimer(2000);
  console.log(timerData, posData);

  setTimer(1000).then(() => {
    console.log('Timer done!');
  });
  console.log('Getting position...');
}

with error handling:

async function trackUserHandler() {
  let posData;
  let timerData;

  try {
    posData = await getPosition();
    timerData = await setTimer(2000);
  } catch(error) {
    console.log(error);
  }
  console.log(timerData, posData);

  setTimer(1000).then(() => {
    console.log('Timer done!');
  });
  console.log('Getting position...');
}
vikramlc commented 4 years ago

Promise operations

// Promise.race([getPosition(), setTimer(1000)]).then(promiseData => {
//   console.log(promiseData);
// });

// Promise.all([getPosition(), setTimer(1000)]).then(promiseData => {
//   console.log(promiseData);
// });

Promise.allSettled([getPosition(), setTimer(1000)]).then(promiseData => {
  console.log(promiseData);
});