Namaste Javascript Notes
Namaste Javascript Notes
In the container the first component is memory component and the 2nd one is code
component
Memory component has all the variables and functions in key value pairs. It is also called
Variable environment.
Code component is the place where code is executed one line at a time. It is also called the
Thread of Execution.
JS is a synchronous, single-threaded language
var n = 2;
function square(num) {
var ans = num * num;
return ans;
}
var square2 = square(n);
var square4 = square(4);
The very first thing which JS does is memory creation phase, so it goes to line one of above
code snippet, and allocates a memory space for variable 'n' and then goes to line two, and
allocates a memory space for function 'square'.
When allocating memory for n it stores 'undefined', a special value for 'n'. For 'square', it
stores the whole code of the function inside its memory space.
Then, as square2 and square4 are variables as well, it allocates memory and stores
'undefined' for them, and this is the end of first phase i.e. memory creation phase.
So Output will look something like :
Now, in 2nd phase i.e. code execution phase, it starts going through the whole code line by
line. As it encounters var n = 2, it assigns 2 to 'n'. Until now, the value of 'n' was undefined.
For function, there is nothing to execute. As these lines werealready dealt with in memory
creation phase.
Coming to line 6 i.e. var square2 = square(n), here functions are a bit different than any
other language. A new execution context is created altogether.
Again in this new execution context, in memory creation phase, we allocate memory to
num and ans the two variables. And undefined is placed in them.
Now, in code execution phase of this execution context, first 2 is assigned to num. Then
var ans = num * num will store 4 in ans. After that, return ans returnsthe control of
program back to where this function was invoked from.
When return keyword is encountered, It returns the control to the called line and
also the function execution context isdeleted. Same thing will be repeated for
square4 and then after that is finished, the global execution context will be
destroyed.
So the final diagram before deletion would look something like:
JavaScript manages code execution context creation and deletion with the the help of Call Stack.
Call Stack is a mechanism to keep track of its place in script that calls multiple functions.
It is also known as Program Stack, Control Stack, Runtime stack, Machine Stack, Execution
context stack.
It should have been an outright error in many other languages, as it is not possible to even
access something whichis not even created (defined) yet But in JS, We know that in memory
creation phase it assigns undefined and puts the content of function to function's memory.
And in execution, it then executes whatever is asked. Here, as execution goes line by line and
not after compiling, it could only print undefined and nothing else. This phenomenon, is not an
error. However, if we remove var x = 7; then it gives error. Uncaught ReferenceError: x is not
defined
Hoisting is a concept which enables us to extract values of variables and functions even before
initializing / assigning value without getting error and this is happening due to the 1st phase
(memory creation phase) of the Execution Context.
So in previous lecture, we learnt that execution context gets created in two phase, so even
before code execution, memory is created so in case of variable, it will be initialized as
undefined while in case of function the whole function code is placed in the memory.
Example :
Now let's observe a different example and try to understand the output
var x = 1;
a();
b(); // we are calling the functions before defining them. This will work
properly, as seen in Hoisting.
console.log(x);
function a() {
var x = 10; // local scope because of separate execution context
console.log(x);
}
function b() {
var x = 100;
console.log(x);
}
Outputs:
10
100
1
Code Flow in terms of Execution Context :
The Global Execution Context (GEC) is created (the big box with Memory and Code subparts). Also
GEC is pushed into Call Stack
In first phase of GEC (memory phase), variable x:undefined and a and b have their entire function
code as value initialized
In second phase of GEC (execution phase), when the function is called, a new local Execution Context
is created. After x = 1 assigned to GEC x, a() is called. So local EC for a is made inside code part of
GEC.
For local EC, a totally different x variable assigned undefined(x inside a()) in phase 1 , and in phase 2
it is assigned 10 and printed in console log. After printing, no more commands to run, so a() local EC
is removed from both GEC and from Call stack
Call Stack :[GEC, b()] -> GEC (after printing yet another totally different x value as 100 in console
log)
Finally GEC is deleted and also removed from call stack. Program ends.
Reference:
Episode 5 : Shortest JS Program, window & this keyword
The shortest JS program is empty file. Because even then, JS engine does a lot of things. As
always, even in this case, it creates the GEC which has memory space and the execution
context.
JS engine creates something known as 'window'. It is an object, which is created in the
global space. It contains lots of functions and variables. These functions and variables can
be accessed from anywhere in the program.
JS engine also creates a this keyword, which points to the window object at the global
level. So, in summary, along with GEC, a global object (window) and a this variable are
created.
In different engines, the name of global object changes. Window in browsers, but in
nodeJS it is called something else. At global level, this === window
If we create any variable in the global scope, then the variables get attached to the global
object.
var x = 10;
console.log(x); // 10
console.log(this.x); // 10
console.log(window.x); // 10
If an object/variable is not even declared/found in memory allocation phase, and tried to access it
then it is Not defined
When variable is declared but not assigned value, its current value is undefined. But when the
variable itself is not declared but called in code, then it is not defined.
JS is a loosely typed / weakly typed language. It doesn't attach variables to any datatype. We can
say var a = 5, and then change the value to boolean a = true or string a = 'hello' later on.
Never assign undefined to a variable manually. Let it happen on it's own accord.
// CASE 1
function a() {
console.log(b); // 10
// Instead of printing undefined it prints 10, So somehow this a function
could access the variable b outside the function scope.
}
var b = 10;
a();
In case 2: 10 is printed. It means that within nested function too, the global scope variable can be
accessed.
// CASE 2
function a() {
c();
function c() {
console.log(b); // 10
}
}
var b = 10;
a();
In case 3: 100 is printed meaning local variable of the same name took precedence over a global
variable.
// CASE 3
function a() {
c();
function c() {
var b = 100;
console.log(b); // 100
}
}
var b = 10;
a();
In case 4: A function can access a global variable, but the global execution context can't access any
local variable.
// CASE 4
function a() {
var b = 10;
c();
function c() {
console.log(b); // 10
}
}
a();
console.log(b); // Error, Not Defined
To summarize the above points in terms of execution context: call_stack = [GEC, a(), c()]
Now lets also assign the memory sections of each execution context in call_stack. c() = [[lexical environment pointer
pointing to a()]]
a() = [b:10, c:{}, [lexical environment pointer pointing to GEC]] GEC = [a:{},[lexical_environment pointer pointing to null]]
So, Lexical Environment = local memory + lexical env of its parent. Hence, Lexical
Environement is the local memory along with the lexical environment of its parent
Lexical: In hierarchy, In order
Whenever an Execution Context is created, a Lexical environment(LE) is also created and is
referenced in the local Execution Context(in memory space).
The process of going one by one to parent and checking for values is called scope chain or
Lexcial environment chain.
function a() {
function c() {
// logic here
}
c(); // c is lexically inside a
} // a is lexically inside global execution
Lexical or Static scope refers to the accessibility of variables, functions and object based on
physical location in source code.
Global {
Outer {
Inner
}
}
// Inner is surrounded by lexical scope of Outer
TLDR ; An inner function can access variables which are in outer functions even if inner
function is nested deep. In any other case, a function can't access variables not in its scope.
let a = 10;
let a = 100; //this code is rejected upfront as SyntaxError. (duplicate
declaration)
-----------
let a = 10;
var a = 100; // this code also rejected upfront as SyntaxError. (can't use
same name in same scope)
Let is a stricter version of var. Now, const is even more stricter than let.
let a;
a = 10;
console.log(a) // 10. Note declaration and assigning of a is in different
lines.
const b;
b = 10;
console.log(b); // SyntaxError: Missing initializer in const declaration.
(This type of declaration won't work with const. const b = 10 only will work)
const b = 100;
b = 1000; //this gives us TypeError: Assignment to constant variable.
{
var a = 10;
let b = 20;
const c = 30;
// Here let and const are hoisted in Block scope,
// While, var is hoisted in Global scope.
}
What is Shadowing ?
var a = 100;
{
var a = 10; // same name as global var let b = 20;
const c = 30;
console.log(a); // 10
console.log(b); // 20
console.log(c); // 30
}
console.log(a); // 10, instead of the 100 we were expecting. So block "a"
modified val of global "a" as well. In console, only b and c are in block
space. a initially is in global space(a = 100), and when a = 10 line is run, a
is not created in block space, but replaces 100 with 10 in global space
itself.
So, If one has same named variable outside the block, the variable inside the block shadows
the outside variable.
This happens only for var
Let's observe the behaviour in case of let and const and understand it's reason.
let b = 100;
{
var a = 10; let b = 20;
const c = 30;
console.log(b); // 20
}
console.log(b);
// 100, Both b's are in separate spaces (one in Block(20) and one in
// Script(another arbitrary mem space)(100)). Same is also true for
*const* declarations.
Same logic is true even for functions
const c = 100;
function x()
{
const c = 10;
console.log(c); // 10
}
x();
console.log(c); // 100
let a = 20;
{
var a = 20;
}
// Uncaught SyntaxError: Identifier 'a' has already been declared
We cannot shadow let with var. But it is valid to shadow a let using a let. However, we can
shadow var with let.
All scope rules that work in function are same in arrow functions too.
Since var is function scoped, it is not a problem with the code below.
let a = 20;
function x()
{
var a = 20;
}
Episode 10 : Closures in JS
function x()
{
var a = 7;
function y() {
console.log(a);
}
return y;
}
var z = x();
console.log(z); // value of z is entire code of function y.
In above code, When y is returned, not only is the function returned but the entire closure
(fun y + its lexical scope) is returned and put inside z. So when z is used somewhere else in
program, it still remembers var a inside x()
Another Example :
function z() {
var b = 900;
function x() {
var a = 7;
function y() {
console.log(a, b);
}
y();
}
x();
}
z(); // 7 900
Output:
Namaste Javascript
1 // after waiting 3 seconds
- We expect JS to wait 3 sec, print 1 and then go down and print the string. But JS prints
string immediately, wait 3 sec and then prints 1.
- The function inside setTimeout forms a closure (remembers reference to i). So wherever
function goes it carries this ref along with it.
- setTimeout takes this callback function & attaches timer of 3000ms and stores it. Goes
to next line without waiting and prints string.
- After 3000ms runs out, JS takes function, puts it into call stack and runs it.
function x() {
for (var i = 1; i <= 5; i++) {
setTimeout(function () {
console.log(i);
}, i * 1000);
}
console.log("Namaste Javascript");
}
x();
// Output:
// Namaste Javascript
// 6
// 6
// 6
// 6
// 6
Reason?
This happens because of closures. When setTimeout stores the function somewhere and
attaches timer to it, the function remembers its reference to i, not value of i.
All 5 copies of function point to same reference of i. JS stores these 5 functions, prints string
and then comes back to the functions. By then the timer has run fully.
And due to looping, the i value became 6. And when the callback fun runs the variable i = 6. So
same 6 is printed in each log
To avoid this, we can use let instead of var as let has Block scope. For each iteration, the i is a
new variable altogether (new copy of i). Everytime setTimeout is run, the inside function forms
closure with new variable i
But what if interviewer ask us to implement using var ?
function x() {
for (var i = 1; i <= 5; i++) {
function close(i) {
setTimeout(function () {
console.log(i);
}, i * 1000);
// put the setT function inside new function close()
}
close(i); // everytime you call close(i) it creates new copy of i. Only
this time, it is with var itself!
}
console.log("Namaste Javascript");
}
x();
function outer() {
var a = 10;
function inner() {
console.log(a);
} // inner forms a closure with outer return inner;
}
outer()(); // 10 // over here first `()` will return inner function and then
using secong `()` to call inner function
Ans: Yes, because inner function forms a closure with its outer environment so sequence
doesn't matter.
function outer() {
let a = 10;
function inner() {
console.log(a);
}
return inner;
}
outer()(); // 10
Q4: Will inner function have the access to outer function argument?
function outer(str) {
let a = 10;
function inner() {
console.log(a, str);
}
return inner;
}
outer("Hello There")(); // 10 "Hello There"
Ans: Inner function will now form closure and will have access to both a and str.
Q5: In below code, will inner form closure with outest?
function outest() {
var c = 20;
function outer(str) {
let a = 10;
function inner() {
console.log(a, c, str);
}
return inner;
}
return outer;
}
outest()("Hello There")(); // 10 20 "Hello There"
Ans: Yes, inner will have access to all its outer environment.
function outest() {
var c = 20;
function outer(str) {
let a = 10;
function inner() {
console.log(a, c, str);
}
return inner;
}
return outer;
}
let a = 100;
outest()("Hello There")(); // 10 20 "Hello There"
Ans: Still the same output, the inner function will have reference to inner a, so conflicting name
won't matter here. If it wouldn't have find a inside outer function then it would have went more
outer to find a and thus have printed 100. So, it try to resolve variable in scope chain and if a
wouldn't have been found it would have given reference error.
// without closures
var count = 0;
function increment(){
count++;
}
// in the above code, anyone can access count and change it.
// ----------------------------------------------------------
// ----------------------------------------------------------
// Adding decrement counter and refactoring code:
function Counter() {
//constructor function. Good coding would be to capitalize first letter of
constructor function.
var count = 0;
this.incrementCounter = function() { //anonymous function
count++;
console.log(count);
}
this.decrementCounter = function() {
count--;
console.log(count);
}
}
var counter1 = new Counter(); // new keyword for constructor function
counter1.incrementCounter();
counter1.incrementCounter();
counter1.decrementCounter();
// returns 1 2 1
// ----------------------------------------------------------
function a() {
var x = 0;
return function b() {
console.log(x);
};
}
var y = a(); // y is a copy of b() y();
// Once a() is called, its element x should be garbage collected ideally.
// But fun b has closure over var x. So mem of x cannot be freed.
//Like this if more closures formed, it becomes an issue. To tacke this,
//JS engines like v8 and Chrome have smart garbage
// collection mechanisms. Say we have var x = 0, z = 10 in above code.
// When console log happens, x is printed as 0 but z is removed
automatically.
Episode 13 : First Class Functions ft. Anonymous Functions
function a() {
console.log("Hello");
}
a(); // Hello
var b = function () {
console.log("Hello");
};
b();
function ()
{
// code
} // this is going to throw Syntax Error - Function Statement requires
function name.
- They don't have their own identity. So an anonymous function without code inside it
results in an error.
- Anonymous functions are used when functions are used as values eg. the code sample
for function expression above.
Q: Parameters vs Arguments ?
Callback Functions
Functions are first class citizens ie. take a function A and pass it to another function B. Here, A is
a callback function. So basically I am giving access to function B to call function A. This callback
function gives us the access to whole Asynchronous world in Synchronous world.
setTimeout(function () {
console.log("Timer");
}, 1000); // first argument is callback function and second is timer.
JS is a synchronous and single threaded language. But due to callbacks, we can do async things
in JS.
setTimeout(function () {
console.log("timer");
}, 5000);
function x(y) {
console.log("x");
y();
}
x(function y() {
console.log("y");
});
// x y timer
- In the call stack, first x and y are present. After code execution, they go away and stack
is empty. Then after 5 seconds (from beginning) anonymous suddenly appear up in
stack ie. setTimeout
- All 3 functions are executed through call stack. If any operation blocks the call stack, its
called blocking the main thread.
- Say if x() takes 30 sec to run, then JS has to wait for it to finish as it has only 1 call
stack/1 main thread. Never block main thread.
- Always use async for functions that take time eg. setTimeout
Event Listener
We will create a button in html and attach event to it.
// index.html
<button id="clickMe">Click Me!</button>;
// in index.js
document.getElementById("clickMe").addEventListener("click", function
xyz() {
//when event click occurs, this callback function (xyz) is called into
callstack console.log("Button clicked");
});
function attachEventList() {
//creating new function for closure let count = 0;
document.getElementById("clickMe").addEventListener("click", function
xyz() {
console.log("Button clicked", ++count); //now callback function forms
closure with outer scope(count)
});
}
attachEventList();
Note: Call stack will execeute any execeution context which enters it. Time, tide and JS waits
for none. TLDR; Callstack has no timer.
Browser has JS Engine which has Call Stack which has Global execution context, local execution
context etc.
- But browser has many other superpowers - Local storage space, Timer, place to enter
URL, Bluetooth access, Geolocation access and so on.
- Now JS needs some way to connect the callstack with all these superpowers. This is
done using Web APIs.
WebAPIs
None of the below are part of Javascript! These are extra superpowers that browser has.
Browser gives access to JS callstack to use these powers.
setTimeout(), DOM APIs, fetch(), localstorage, console (yes, even console.log is not JS!!), location
and so many more.
- setTimeout() : Timer function
- DOM APIs : eg.Document.xxxx ; Used to access HTML DOM tree. (Document Object
Manipulation) fetch() : Used to make connection with external servers eg. Netflix servers
etc.
We get all these inside call stack through global object ie. window
- Use window keyword like : window.setTimeout(), window.localstorage,
window.console.log() to log something inside console.
- As window is global obj, and all the above functions are present in global object, we
don't explicity write window but it is implied.
Let's undertand the below code image and its explaination:
First a GEC is created and put inside call stack.
console.log("start");
setTimeout(function cb() {
console.log("timer");
}, 5000);
console.log("end");
// start end timer
- console.log("Start"); // this calls the console web api (through window) which in turn
actually modifies values in console.
- setTimeout(function cb() { //this calls the setTimeout web api which gives access to
timer feature. It stores the callback cb() and starts timer. console.log("Callback");}, 5000);
- console.log("End"); // calls console api and logs in console window. After this GEC pops
from call stack.
- While all this is happening, the timer is constantly ticking. After it becomes 0, the
callback cb() has to run.
- Now we need this cb to go into call stack. Only then will it be executed. For this we need
event loop and Callback queue
Explaination?
console.log("Start");
document.getElementById("btn").addEventListener("click", function cb() {
// cb() registered inside webapi environment and event(click) attached to it.
i.e.
// REGISTERING CALLBACK AND ATTACHING EVENT TO IT.
console.log("Callback");
});
console.log("End");
// calls console api and logs in console window. After this GEC get removed
from call stack.
// In above code, even after console prints "Start" and "End" and pops GEC
out, the eventListener stays in webapi env(with hope that user may click it
some day) until explicitly removed, or the browser is closed
Eventloop has just one job to keep checking callback queue and if found something push it to
call stack and delete from callback queue.
Q: Need of callback queue?
Ans: Suppose user clciks button x6 times. So 6 cb() are put inside callback queue. Event loop
sees if call stack is empty/has space and whether callback queue is not empty(6 elements here).
Elements of callback queue popped off, put in callstack, executed and then popped off from call
stack.
Behaviour of fetch (Microtask Queue?)
Let's observe the code below and try to understand
console.log("Start"); // this calls the console web api (through window) which
in turn actually modifies values in console.
setTimeout(function cbT() {
console.log("CB Timeout");
}, 5000);
fetch("https://api.netflix.com").then(function cbF() { console.log("CB
Netflix");
}); // take 2 seconds to bring response
// millions lines of code console.log("End");
Code Explaination:
* Same steps for everything before fetch() in above code.
* fetch registers cbF into webapi environment along with existing cbT.
* cbT is waiting for 5000ms to end so that it can be put inside callback queue.
cbF is waiting for data to be returned from Netflix servers gonna take 2 seconds.
* After this millions of lines of code is running, by the time millions line of code will execute, 5
seconds has finished and now the timer has expired and response from Netflix server is ready.
* Data back from cbF ready to be executed gets stored into something called a Microtask
Queue.
* Also after expiration of timer, cbT is ready to execute in Callback Queue.
* Microtask Queue is exactly same as Callback Queue, but it has higher priority. Functions in
Microtask Queue are executed earlier than Callback Queue.
* In console, first Start and End are printed in console. First cbF goes in callstack and "CB Netflix"
is printed. cbF popped from callstack. Next cbT is removed from callback
Queue, put in Call Stack, "CB Timeout" is printed, and cbT removed from callstack.
See below Image for more understanding
Microtask Priority Visualization
Code inside Javascript Engine passes through 3 steps : Parsing,Compilation and Execution
1. Parsing - Code is broken down into tokens. In "let a = 7" -> let, a, =, 7 are all tokens. Also we
have a syntax parser that takes code and converts it into an AST (Abstract Syntax Tree) which is
a JSON with all key values like type, start, end, body etc (looks like package.json but for a line of
code in JS. Kinda unimportant)(Check out astexplorer.net -> converts line of code into AST).
3. Execution - Needs 2 components ie. Memory heap(place where all memory is stored) and
Call Stack(same call stack from prev episodes). There is also a garbage collector. It uses an algo
called Mark and Sweep.
GiF Demo
Companies use different JS engines and each try to make theirs the best.
v8 of Google has Interpreter called Ignition, a compiler called Turbo Fan and garbage
collector called Orinoco
v8 architecture:
Episode 17 : Trust issues with setTimeout()
setTimeout with timer of 5 secs sometimes does not exactly guarantees that the callback
function will execute exactly after 5s.
Let's observe the below code and it's explaination
console.log("Start");
setTimeout(function cb() {
console.log("Callback");
}, 5000);
console.log("End");
// Millions of lines of code to execute
// o/p: Over here setTimeout exactly doesn't guarantee that the callback
function will be called exactly after 5s. Maybe 6,7 or even 10! It all depends
on callstack. Why?
Reason?
First GEC is created and pushed in callstack.
Start is printed in console
When setTimeout is seen, callback function is registered into webapi's env. And timer is
attached to it and started. callback waits for its turn to be execeuted once timer expires.
But JS waits for none. Goes to next line.
End is printed in console.
After "End", we have 1 million lines of code that takes 10 sec(say) to finish execution. So
GEC won't pop out of stack. It runs all the code for 10 sec.
But in the background, the timer runs for 5s. While callstack runs the 1M line of code,
this timer has already expired and callback fun has been pushed to Callback queue and
waiting to pushed to callstack to get executed.
Event loop keeps checking if callstack is empty or not. But here GEC is still in stack so cb
can't be popped from callback Queue and pushed to CallStack. Though setTimeout is
only for 5s, it waits for 10s until callstack is empty before it can execute (When GEC
popped after 10sec, callstack() is pushed into call stack and immediately executed
(Whatever is pushed to callstack is executed instantly).
This is called as the Concurrency model of JS. This is the logic behind setTimeout's trust
issues.
The First rule of JavaScript: Do not block the main thread (as JS is a single threaded(only 1
callstack) language). In below example, we are blocking the main thread. Observe Questiona
and Output.
setTimeout guarantees that it will take at least the given timer to execute the code.
JS is a synchronous single threaded language. With just 1 thread it runs all pieces of code. It
becomes kind of an interpreter language, and runs code very fast inside browser (no need to
wait for code to be compiled) (JIT - Just in time compilation). And there are still ways to do
async operations as well.
What if timeout = 0sec?
console.log("Start");
setTimeout(function cb() {
console.log("Callback");
}, 0);
console.log("End");
// Even though timer = 0s, the cb() has to go through the queue. Registers
calback in webapi's env , moves to callback queue, and execute once callstack
is empty.
// O/p - Start End Callback
// This method of putting timer = 0, can be used to defer a less imp function
by a little so the more important function(here printing "End") can take place
Episode 18 : Higher-Order Functions ft. Functional
Programming
function x() {
console.log("Hi)";
};
function y(x) { x();
};
y(); // Hi
// y is a higher order function
// x is a callback function
Let's try to understand how we should approach solution in interview. I have an array of radius
and I have to calculate area using these radius and store in an array.
First Approach:
const radius = [1, 2, 3, 4];
const calculateArea =
function (radius) {
const output = [];
return output;
};
console.log(calculateArea(radius));
The above solution works perfectly fine but what if we have now requirement to calculate array
of circumference. Code now be like :
const radius = [1, 2, 3, 4];
const calculateCircumference =
function (radius) {const
output = [];
return output;
};
console.log(calculateCircumference(radius));
But over here we are violating some principle like DRY Principle, now lets observe the better
approach.
return x * 2;
So basically map function is mapping each and every value and transforming it based on given
condition.
Filter function :
Filter function is basically used to filter the value inside an array. The arr.filter() method is used
to create a new array from a given array consisting of only those elements from the given array
which satisfy a condition set by the argument method.
const array = [5, 1, 3, 2, 6];
// filter odd values
function isOdd(x) {
return x % 2;
}
const oddArr = array.filter(isOdd); // [5,1,3]
Filter function creates an array and store only those values which evaluates to true.
Reduce function
It is a function which take all the values of array and gives a single output of it. It reduces the
array to give a single output.
function findSum(arr) {
let sum = 0;
for (let i = 0; i < arr.length; i++) {
sum = sum + arr[i];
}
return sum;
}
console.log(findSum(array)); // 17
// using reduce
const output = arr.reduce((acc, current) => {
if (current > acc) {
acc = current;
}
return acc;
}, 0);
console.log(output); // 6
// acc is just a label which represent the accumulated value till now,
// so we can also label it as max in this case
const outputs = arr.reduce((max, current) => {
if (current > max) {
max = current;
}
return max;
}, 0);
console.log(outputs); // 6
Tricky MAP :
const users = [
{ firstName: "Alok", lastName: "Raj", age: 23 },
{ firstName: "Ashish", lastName: "Kumar", age: 29 },
{ firstName: "Ankit", lastName: "Roy", age: 29 },
{ firstName: "Pranav", lastName: "Mukherjee", age: 50 },
];
// Get array of full name : ["Alok Raj", "Ashish Kumar", ...]
const fullNameArr = users.map((user) => user.firstName + " " + user.lastName);
console.log(fullNameArr); // ["Alok Raj", "Ashish Kumar", ...]
// Get the count/report of how many unique people with unique age are there
// like: {29 : 2, 75 : 1, 50 : 1}
// We should use reduce, why? we want to deduce some information from the
array.
// Basically we want to get a single object as output
const report = users.reduce((acc, curr) => {
if (acc[curr.age]) {
acc[curr.age] = ++acc[curr.age];
} else {
acc[curr.age] = 1;
}
return acc; //to every time return update object
}, {});
console.log(report); // {29 : 2, 75 : 1, 50 : 1}
Function Chaining
const users = [
{ firstName: "Alok", lastName: "Raj", age: 23 },
{ firstName: "Ashish", lastName: "Kumar", age: 29 },
{ firstName: "Ankit", lastName: "Roy", age: 29 },
{ firstName: "Pranav", lastName: "Mukherjee", age: 50 },
];
// function chaining
1.Good Part of callback - Callback are super important while writing asynchronous code in JS
2.Bad Part of Callback - Using callback we can face issue:
Callback Hell
Inversion of control
Understanding of Bad part of callback is super important to learn Promise in next lecture.
� JavaScript is synchronous, single threaded language. It can Just do one thing at a time, it has just one
call-stackand it can execute one thing at a time. Whatever code we give to Javascript will be quickly executed
by Javascript engine, 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 execution of any line, we could utilize callback, How?
console.log("Namaste");
setTimeout(function () {
console.log("JavaScript");
}, 5000);
console.log("Season 2");
// Namaste
// Season 2
// JavaScript
// 1. Create a Order
// 2. Proceed to Payment
// It could look
something like
this:
api.createOrder()
;
api.proceedToPayment();
Assumption, once order is created then only we can proceed to payment, so there is a
dependency. So How to manage this dependency. Callback can come as rescue, How?
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 payment is done, you have to show Order summary
by calling
api.showOrderSummary() and now it has dependency on api.proceedToPayment() Now my
code should look something like this:
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
When we have a large codebase and multiple apis and have dependency on each other, then
we fall into callback hell. These codes are tough to maintain. These callback hell structure is also
known as Pyramid of Doom.
Till this point we are comfortable with concept of callback hell but now lets discuss about
Inversion of Control. It is very important to understand in order to get comfortable around the
concept of promise.
� Inversion of control is like that you lose the control of code when we are using callback.
So over here, we are creating a order and then we are blindly trusting `createOrder` to
call `proceedToPayment`.
It is risky, as `proceedToPayment` is important part of code and we are blindly trusting
`createOrder` to call it and handle it.
When we pass a function as a callback, basically we are dependant on our parent
function that it is his responsibility to run that function. This is called `inversion of
control` because we are dependant on that function. What if parent function stopped
working, what if it was developed by another programmer or callback runs two times or
never run at all.
In next session, we will see how we can fix such problems.
more at http://callbackhell.com/
We will discuss with code example that how things used to work before Promises and then how
it works after Promises
Suppose, taking an example of E-Commerce :
// Below two functions are asynchronous and dependent on each other const
orderId = createOrder(cart);
proceedToPayment(orderId);
Now, we will make createOrder function return a promise and we will capture that promise into
a variable
Promise is nothing but we can assume it to be empty object with some data value in it, and this
data value will hold whatever this createOrder function will return.
Since createOrder function is an async function and we don't know how much time will it take
to finish execution.
So the moment createOrder will get executed, it will return you a undefined value. Let's say after
5 secs execution finished so now orderId is ready so, it will fill the undefined value with the
orderId.
In short, When createOrder get executed, it immediately returns a promise object with
undefined value. then javascript will continue to execute with other lines of code. After
sometime when createOrder has finished execution and orderId is ready then that will
automatically be assigned to our returned promise which was earlier undefined.
// {data: undefined}
// Initially it will be undefined so below code won't trigger
// After some time, when execution has finished and promiseRef has the data
then automatically the below line will get triggered.
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. But with promise, we are attaching a callback function to a promiseObject.
There is difference between these words, passing a function and attaching a function.
Promise guarantee, it will callback the attached function once it has the fulfilled data. And it will
call it only once. Just once.
Earlier we talked about promise are object with empty data but that's not entirely true, Promise
are much more than that. Now let's understand and see a real promise object.
fetch is a web-api which is utilized to make api call and it returns a promise.
We will be calling public github api to fetch data https://api.github.com/users/alok722
user.then(function (data) {
console.log(data);
});
Q. What is Promise?
Promise object is a placeholder for certain period of time until we receive value from
asynchronous operation.
A container for a future value.
A Promise is an object representing the eventual completion or failure of an 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);
});
// � Common PitFall
// We forget to return promise in Promise Chaining
// The idea is promise/data returned from one .then become data for next .then
// So,
createOrder(cart)
.then(function (orderId) {
return proceedToPayment(orderId);
})
.then(function (paymentInf) {
return showOrderSummary(paymentInf);
})
.then(function (balance) {
return updateWalletBalance(balance);
});
promise.then(function (orderId) {
proceedToPayment(orderId);
});
Over above, if your validateCart is returning true, so the above promise will be resolved
(success),
function createOrder(cart) {
const promise = new Promise(function (resolve, reject) {
if (!validateCart(cart)) {
const err = new Error("Cart is not Valid");
reject(err);
}
const orderId = "12345";
if (orderId) {
resolve(orderId);
}
});
return promise;
}
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 aka resolved promise handling
proceedToPayment(orderId);
})
.catch(function (err) {
// � failure aka reject handling
console.log(err);
});
function proceedToPayment(cart) {
return new Promise(function (resolve, reject) {
// For time being, we are simply `resolving` promise
resolve("Payment Successful");
});
}
Q: What if we want to continue execution even if any of my promise is failing, how to
achieve this?
-> By placing the .catch block at some level after which we are not concerned with failure.
-> There could be multiple .catch too. Eg:
createOrder(cart)
.then(function (orderId) {
// � success aka resolved promise handling
proceedToPayment(orderId);
return orderId;
})
.catch(function (err) {
// � Whatever fails below it, catch wont care
// this block is responsible for code block above it.
console.log(err);
})
.then(function (orderId) {
// Promise chaining
// � we will make sure that `proceedToPayment` returns a promise too
return proceedToPayment(orderId);
})
.then(function (paymentInfo) {
// from above, `proceedToPayment` is returning a promise so we can consume
using `.then`
console.log(paymentInfo);
});