Namaste JS Season-2 Notes
Namaste JS Season-2 Notes
Namaste JS Season-2 Notes
1. Good Part of Callback - Callbacks are super important while writing asynchronous code in JS
2. Bad Part of Callback - Callbacks inside Callback leads to issues like -
- Callback Hell
- Inversion of control
- Understanding Bad part of Callback is super important to learn Promise in next chapter.
> 💡 JavaScript is synchronous, single threaded language. It can Just do one thing at a time. JS Engine
has just one call-stack where it executes code line by line, it does not wait.
console.log("Namaste");
console.log("JavaScript");
console.log("Season 2");
// Namaste
// JavaScript
// Season 2
// 💡 It is quickly printing because `Time, tide & JavaScript waits for none. `
But what if we have to delay code execution of any line. We could utilize Callback.
console.log("Namaste");
setTimeout(function () {
console.log("JavaScript");
}, 5000);
console.log("Season 2");
// Namaste
// Season 2
// JavaScript
Assume a scenario of e-Commerce web, where one user is placing order, he has added items like,
shoes, pants and kurta in cart and now he is placing order. So, in backend the situation could look
something like this.
Assume, once order is created then only we can proceed to payment, so there is a dependency. So
How to manage this dependency.
api.createOrder(cart, function () {
api.proceedToPayment();
});
// 💡 Over here `createOrder` api is first creating a order then it is responsible to call
`api.proceedToPayment()` as part of callback approach.
To make it a bit complicated, what if after the payment, you have to show Order summary by calling
`api.showOrderSummary()` and now it has dependency on `api.proceedToPayment()`
api.createOrder(cart, function () {
api.proceedToPayment(function () {
api.showOrderSummary();
});
});
Now what if we have to update the wallet, now this will have a dependency over
`showOrderSummary`
api.createOrder(cart, function () {
api.proceedToPayment(function () {
api.showOrderSummary(function () {
api.updateWallet();
});
});
});
// 💡 Callback Hell - Callbacks inside Callback creates a call back hell structure.
When we have a large codebase having numbers of APIs with internal dependencies to each other,
then we fall into Callback hell where the code will grow horizontally and it is very difficult to read
and maintain. This Callback hell structure is also known as Pyramid of Doom.
Till this point we are comfortable with concept of Callback hell but now let’s discuss about `Inversion
of Control`. It is very important to understand in order to get comfortable around the concept of
promise.
INVERSION OF CONTROL –
> 💡 Inversion of control is like, you lose the control of code when we are using Callbacks.
api.createOrder(cart, function () {
api.proceedToPayment();
});
// 💡 So over here, we are creating an order and then we are blindly trusting `createOrder` API to
call `proceedToPayment`.
// 💡 When we pass a function as a Callback, basically we are dependent on our parent function that
it is his responsibility to run that function. This is called `inversion of control` because we are
dependent on that function. What if parent function stopped working, what if it was developed by
another programmer or Callback runs two times or it never runs at all.
We will discuss with code example that how things used to work before `Promises` and then how it
works after `Promises`
// In code below, it is the responsibility of createOrder function to create the order first and call the
Callback function proceedToPayment later. But with this approach we have a problem called
`Inversion of Control (discussed on previous chapter) `
createOrder(cart, function () {
proceedToPayment(orderId);
});
Now, we will make `createOrder` function returning a promise object and we will capture that
`promise` into a `variable`.
Promise is nothing but a plain javascript object having properties such as Prototype, PromiseState
and PromiseResult . PromiseResult will hold data whatever this `createOrder` function will return.
Since `createOrder` function is an async function, we don't know how much time will it take to finish
execution.
So, the moment `createOrder` API gets executed, it will return an `undefined` value. Let's say after 5
seconds, execution got finished and `orderId` value is available. Now this undefined value will be
replaced by the actual data i.e. `orderId`.
In short, when `createOrder` got executed, it immediately returned a `promise object` with
`undefined` value. Then JavaScript continued to execute next lines of code. After sometime when
`createOrder` finished its execution, `orderId` data was available and the earlier value undefined got
replaced with actual order Id data.
Ans: we usually attach a `Callback` function to the `promise object` using `then` that get triggered
automatically when `result` is ready.
promiseRef.then(function () {
proceedToPayment(orderId);
});
In Earlier solution we used to pass the function and then used to trust the function to execute the
Callback.
There is difference between these words passing a function and attaching a function.
Promise guarantees that it will call the then attached Callback function only once when the data is
available. We call it as Promise is fulfilled or resolved. And if the data is not available then Promise
will call the catch attached Callback function and we call it as promise is rejected.
Earlier we talked about promise is an object with empty data but that's not entirely true, `Promise` is
much more than that.
fetch is a web-API which is utilized to make API call and it returns a promise.
/ OBSERVATIONS:
/
* When above line is executed, `fetch` makes API call and return a `promise` instantly which is in
`Pending` state and JavaScript doesn't wait to get it `fulfilled`
* And in next line it consoles out the `pending promise`.
* NOTE: chrome browser has some in-consistency, the moment console happens it shows in
pending state but if you will expand that it will show fulfilled because chrome updated the log when
promise get fulfilled.
* Once fulfilled, data is available in promiseResult and this data is not directly accessible to the
external world. This data is in readable stream format and there is a way to extract it.
*/
Using `. then`
user.then(function (data) {
console.log(data);
});
// And this is how Promise is used. It guarantees that it could be resolved only once, either it could
be `success` or `failure`
/
*Promise State-
*/
💡Promise Objects are immutable.
-> Once promise is fulfilled, we get the data. We don't have to worry that someone can
mutate/change that data because by nature promise is immutable.
-> Promise object is a placeholder for certain period of time until we receive value from
asynchronous operation.
We are now done solving one issue of Callback i.e. Inversion of Control. But there is one more issue,
Callback hell.
// And now above code is expanding horizontally and this is called pyramid of doom. Callback hell is
ugly and hard to maintain. Promise fixes this issue too using `Promise Chaining`.
// Example Below is a Promise Chaining
createOrder(cart)
.then(function (orderId) {
proceedToPayment(orderId);
})
.then(function (paymentInf) {
showOrderSummary(paymentInf);
})
.then(function (balance) {
updateWalletBalance(balance);
});
// The idea is promise data returned can be used in the next promise object in the promise chain and
so on. We are passing the promise data down to the promise chain and we need return keyword for
that to happen.
createOrder(cart)
.then(function (orderId) {
return proceedToPayment(orderId);
})
.then(function (paymentInf) {
return showOrderSummary(paymentInf);
})
.then(function (balance) {
return updateWalletBalance(balance);
});
// To improve readability, you can use arrow function instead of regular function
promise 1
const createOrderPromise = createOrder(cart) // we get the order ID after the order is created
promise 2
const proceedToPaymentPromise = createOrderPromise.then(
function (orderId) {
return proceedToPayment(orderId);
}) // we passed the object ID from the promise object into proceedToPayment() which will return
another promise
promise 3
const ShowOrderSummaryPromise = proceedToPaymentPromise.then(function (paymentInf) {
return showOrderSummary(paymentInf);
}) // The process continues
promise 4
const UpdateWalletBanalcePromise = ShowOrderSummaryPromise.then(function (balance) {
return updateWalletBalance(balance);
}); // The process continues …
# Chapter 22: Creating a Promise, Chaining & Error
Handling
promise - consumer part - How a promise is consumed -
const cart = ["shoes", "pants", "kurta"];
promise.then(function (orderId) {
proceedToPayment(orderId);
})
// Now we will see, how createOrder is implemented so that it is returning a promise. In short, we
will see, "How we can create Promise" and then return it.
In code above, if the cart is validated successfully, the promise will be resolved (success case),
Now let's see if there was some error and we are rejecting the promise, how we could catch that?
-> Using `. catch`
// Here we are consuming Promise and will try to catch promise error
promise
.then(function (orderId) {
// ✅ success- promise resolved case
proceedToPayment(orderId);
})
.catch(function (err) {
// ⚠️ failure - promise reject case
console.log(err);
});
-> In promise chaining, whatever is returned from the first promise becomes data for the next
promise and so on.
-> At any point of promise chaining, if one of the promises is rejected, the execution will fall back to
`. catch` provided there is only one `catch` exist at the end of promise chain. When this happens,
others promise in the promise chain won't run. Let’s take an example.
createOrder(cart)
.then(function (orderId) {
// ✅ success - promise is resolved
console.log(orderId);
return orderId;
})
.then(function (orderId) {
// ✅ success - promise is resolved
return proceedToPayment(orderId);
})
.then(function (paymentInfo) {
// ✅ success - promise is resolved
console.log(paymentInfo);
})
// Handling promise error. Below code will be executed in case either one of the above promises is
rejected. But in our case intentionally we are resolving all the promises. Thus, below code will not be
executed.
.catch(function (err) {
console.log(err);
});
function createOrder(cart) {
const promise = new Promise(function (resolve, reject) {
// If `validateCart` returns false then the promise will be rejected and then our browser will throw
the error if it is not handled properly. For now, assume validateCart returns true.
if (!validateCart(cart)) {
const err = new Error("Cart is not Valid");
reject(err);
}
const orderId = "12345";
if (orderId) {
resolve(orderId);
}
});
return promise;
}
function proceedToPayment(cart) {
return new Promise(function (resolve, reject) {
// For the time being, we are simply `resolving` this promise
resolve("Payment Successful");
});
}
Q: What if we want to continue execution even if any of the promise is failing, how to achieve
this?
-> By placing the `.catch` block next to the promise which has a chance of failure.
-> once `.catch` handles the promise JS will keep executing the next promise in the promise chain.
Example-
createOrder(cart)
.then(function (orderId) {
console.log(orderId);
return orderId; // ⚠️ lets say the promise is rejected here.
})
.catch(function (err) {
// This catch block is only responsible for handling errors in above promise or promises along the
chain. In our case we have one promise and we encountered error and our catch block here is
handling that error.
console.log(err);
});
.then(function (orderId) {
console.log('This block is definitely executed')
return proceedToPayment(orderId);
})
.then(function (paymentInfo) {
console.log(paymentInfo);
})
Note- We can only resolve a promise once. not more than that.
# Chapter 23 - Async & Await
Topics Covered:
- What is async?
- What is await?
- How async await works behind the scenes?
- Example of using async/await
- Error Handling
- Interviews
- Async await vs Promise.then/.catch
Q: What is async?
💡 Async function always returns a promise, even if I return a simple string from async function,
async keyword will wrap it under Promise object and then returns whereas Normal functions return
anything depending on what the context is . It evens returns a promise out of it.
//❓How to extract data from above promise? One way is using promise.then()
dataPromise.then(res => console.log(res)); // Namaste JavaScript
Q: How we used to handle promises earlier and why do we even need async/await?
function getData() {
p.then(res => console.log(res));
}
await function() {} // Syntax error: await is only valid under async function.
Q: What makes `async`-`await` special?
A: Let's understand with one example where we will compare async-await way of resolving promise
with older .then/.catch fashion. For that we will modify our promise `p` with some delay factor
added.
function getData() {
// JS engine will not wait for promise to be resolved
p.then(res => console.log(res));
console.log('Hello There!');
}
getData();
// Output :
// Hello There!
// Promise resolved value!!
Code Explanation:
As we know JavaScript waits for none, so JS engine will register this promise Callback in a separate
space and attaches a Timer of 3 sec to it and then it moves to the next line and prints 'Hello There' in
the console. Once 3 sec is elapsed promise data is available, after that then () is invoked which in
turns logs the promise response in the console.
// This time `Hello There!` won't be printed immediately instead after 3 secs `Hello There!` will be
printed followed by 'Promise resolved value!!'
// 💡 So basically code was waiting at `await` line to get the promise resolve before moving on to
next line.
// In above code example, will our program wait for 2 time or will it execute parallelly.
//📌 `Hi` printed instantly -> now code will wait for 3 secs -> After 3 secs both promises will be
resolved so ('Hello There!' 'Promise resolved value!!' 'Hello There! 2' 'Promise resolved value!!') will
get printed immediately.
// Let's create one promise and then resolve two different promise.
// 📌 `Hi` printed instantly -> now code will wait for 3 secs -> After 3 secs both promises will be
resolved so ('Hello There!' 'Promise resolved value!!' 'Hello There! 2' 'Promise resolved value by
p2!!') will get printed immediately. So even though `p2` was resolved after 2 secs it had to wait for
`p` to get resolved
// Now let's reverse the order execution of promise and observe response.
// 📌 `Hi` printed instantly -> now code will wait for 2 secs -> After 2 secs ('Hello There!' 'Promise
resolved value by p2!!') will get printed and in the subsequent second i.e. after 3 secs ('Hello There!
2' 'Promise resolved value!!') will get printed
A: As we know, Time, Tide and JS wait for none. And it's true. Over here it appears that JS engine is
waiting but JS engine is not waiting over here. It has not occupied the call stack if that would have
been the case our page may have got frozen. So, JS engine is not waiting. So, if it is not waiting then
what it is doing behind the scene? Let's understand with below code snippet.
// When this function is executed, it will go line by line as JS is synchronous single threaded
language. Lets observe what is happening under call-stack. Above you can see we have set the
break-points.
Code Flow:
*In the next line as soon as JavaScript engine sees the await keyword, where promise is supposed to
be resolved, will it wait for promise to resolve and block call stack? No, it does not but it suspends
the execution of handlePromise () till the promise is resolved and moved out of call stack.
*when `p` gets resolved after 5 secs, handlePromise () will be pushed to call-stack. But this time JS
Engine will start executing code from where it was left off. Now it will log 'Hello There!' and 'Promise
resolved value!!'
*In the next line it will check whether `p2` is resolved or not. but P2 will take 10 secs to be resolved.
The point JavaScript engine reaches to this line 5 section had already elapsed so it will take 5 more
seconds to resolve promise for P2. same process will repeat. Execution will be suspended until
promise is resolved.
* 📌 the important point is JS is not waiting, call stack is not getting blocked. But it appears to be
waiting. (Imaginative)
* Moreover, in above scenario what if p1 would be taking 10 secs and p2 5 secs -> even though p2
got resolved earlier but JS is synchronous single threaded language so it will first wait for p1 to be
resolved, once p1 is resolved it prints Hello There and promise p1 object value and then immediately
execute p2 along with the subsequent lines of code.
// In above whenever any error will occur the execution will move to catch block. One could try
above with bad URL which will result in error.
In our case, Promise.all() makes three API calls for three promises in parallel and wait for all the
promises to get resolved. So, this API will take time for the promise which takes maximum time to
get resolved. Promise.all() returns an array of promise results.
Case 2: If any/some/all of the promises are rejected.
If any one of the promises is failed, then this API will throw an error and it will not wait for the other
promises to get settled (resolved/rejected). Whatever error it will get from the rejected promise, the
API will throw the same error as a result.
Promise.allSettled(<Iterable>) –
Case 1: If all of the promises are resolved.
In our case, Promise.allSettled() makes three API calls for three promises in parallel and wait for all
the promises to get resolved. So, it will take time for the promise which takes maximum time to get
resolved. Promise.allSettled() returns an array of promise results.
Case 2: If one/some of the promises are rejected.
If one/some of the promises is/are rejected, then this API will wait for other promises to get
settled(resolved/rejected), then returns the array of promise results.
Promise.race(<Iterable>) –
The promise who will finish first or get settled first will be the winner.
The API will return first settled promise result irrespective of whether the promise is resolved or
rejected.
Promise.any(<Iterable>) – (Success seeking API- receives only the
first successed promise value)
Similar to race API, the only difference is any API will wait for the first promise to get successful. It
returns the first successful promise result.
As soon as one of the promises is resolved, any API returns the resolved promise result.
Case 2: If all of the promise is rejected
This API will return an aggregate error which will be an array of all three errors thrown by all three
promises.
Meaning of promise has been settled –
Promise is either rejected or resolved.