- Notifications
You must be signed in to change notification settings - Fork0
ahelmi365/GET-JS-Talk
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
In this repository, I will be talking about JavaScript and its features. I will be covering the following topics:
- Data types in JavaScript
- Strings in JavaScript
- Declaring Objects in JS
- Copy Objects in JS
- Declaring Functions in js
- Types of functions in JS
- Functions are First Class Objects
- JS is single-threaded programming language
- Callback Functions
- Higher Order Functions
- JS built-in HOF
- Promises in js
- Callback Hell and how to avoid it
Examples of primitive and non-primitive data types in #"auto">
Primitive data types
Non-primitive data types
Primitive Data Types | Non-Primitive Data Types |
---|---|
String | Object |
Number | Array |
Boolean | Function |
Null | Date |
Undefined | Set |
Symbol | Map |
- Boolean: A boolean represents a logical value of either
true
orfalse
.
letisRaining=true;// boolean valueconsole.log(typeofisRaining);// "boolean"
- Number: A number represents a numeric value, including integers and floating-point numbers.
letage=30;// integerletprice=9.99;// floating-point numberconsole.log(typeofage);// "number"console.log(typeofprice);// "number"
- String: A string represents a sequence of characters.
letmessage="Hello, world!";// stringconsole.log(typeofmessage);// "string"
- Undefined: A variable that has been declared but has not been assigned a value is
undefined
.
letfirstName;// undefinedconsole.log(typeoffirstName);// "undefined"
- Null: A variable that is explicitly assigned the value
null
represents an intentional absence of any object value.
letmiddleName=null;// nullconsole.log(typeofmiddleName);// "object"
- Symbol: A symbol represents a unique identifier.
constid=Symbol("id");// symbolconsole.log(typeofid);// "symbol"
- Object: An object is a collection of key-value pairs and represents a complex entity or data structure.
constperson={firstName:"John",lastName:"Doe",age:30,hobbies:["reading","coding","traveling"],};// objectconsole.log(typeofperson);// "object"
- Array: An array is an ordered list of values.
constnumbers=[1,2,3,4,5];// arrayconsole.log(typeofnumbers);// "object"
- Function: A function is a reusable block of code that performs a specific task.
functiongreet(name){console.log(`Hello,${name}!`);}greet("Alice");// logs "Hello, Alice!"greet("Bob");// logs "Hello, Bob!"
- Date: A date represents a specific moment in time.
constnow=newDate();// current date and timeconsole.log(typeofnow);// "object"
- Set: A set is a collection of unique values.
constset=newSet([1,2,3,4,5]);// setconsole.log(typeofset);// "object"
- Map: A map is a collection of key-value pairs.
constmap1=newMap();map1.set("a",1);map1.set("b",2);map1.set("c",3);console.log(map1.get("a"));// Expected output: 1map1.set("a",97);console.log(map1.get("a"));// Expected output: 97console.log(map1.size);// Expected output: 3map1.delete("b");console.log(map1.size);// Expected output: 2
constmap=newMap([["a",1],["b",2],["c",3],]);// mapconsole.log(map);// Map(3) {"a" => 1, "b" => 2, "c" => 3}console.log(typeofmap);// "object"
Primitive Data Types | Non-Primitive Data Types |
---|---|
Immutable | Mutable |
Passed by value | Passed by reference |
Stored in stack | Stored in heap |
Copied by value | Copied by reference |
Compared by value | Compared by reference |
Accessed by value | Accessed by reference |
- Here are some code examples to illustrate the differences between primitive data types and non-primitive data types in #"auto">
- Immutable vs Mutable
- Passed by value vs Passed by reference
- Stored in stack vs Stored in heap
- Copied by value vs Copied by reference
- Compared by value vs Compared by reference
- Accessed by value vs Accessed by reference
Primitive data types are immutable, meaning their value cannot be changed after they are created:
leta="Hello";a[0]="J";// This has no effect, a remains "Hello"console.log(a);// Output: "Hello"
Non-primitive data types are mutable, meaning their values can be changed after they are created:
letobj={name:"John"};obj.name="Jane";// Changes the value of the 'name' propertyconsole.log(obj);// Output: {name: "Jane"}
Primitive data types are passed by value, meaning a copy of their value is passed to a function:
functionchangeValue(a){a=10;}letx=5;changeValue(x);console.log(x);// Output: 5
Non-primitive data types are passed by reference, meaning a reference to their memory location is passed to a function:
functionchangeValue(obj){obj.name="Jane";}letperson={name:"John"};changeValue(person);console.log(person);// Output: {name: "Jane"}
Primitive data types are stored in the stack:
leta=5;
Non-primitive data types are stored in the heap:
letobj={name:"John"};
Primitive data types are copied by value:
leta=5;letb=a;// Copies the value of a to bb=10;console.log(a);// Output: 5
Non-primitive data types are copied by reference:
letobj1={name:"John"};letobj2=obj1;// Copies the reference to obj1 to obj2obj2.name="Jane";console.log(obj1);// Output: {name: "Jane"}
Primitive data types are compared by value:
leta=5;letb=5;console.log(a===b);// Output: true
Non-primitive data types are compared by reference:
letobj1={name:"John"};letobj2={name:"John"};console.log(obj1===obj2);// Output: false
Primitive data types are accessed by value:
leta=5;letb=a;// Copies the value of a to bb=10;console.log(a);// Output: 5
Non-primitive data types are accessed by reference:
letobj={name:"John"};console.log(obj.name);// Output: "John"
Strings are a sequence of characters, which can be either alphabets, numbers, special characters, or a combination of all three. They are used to represent text in a program.
Strings are immutable, meaning their values cannot be changed after they are created.
Strings are primitive data types in JavaScript, which means they are copied by value.
Strings are stored in the stack.
Strings are compared by value.
Strings are accessed by value.
- Strings can be declared using single quotes, double quotes, or backticks. Here are some examples:
letstr1="Hello";letstr2="World";letstr3=`Hello World`;
- The backtick is a new addition to JavaScript, and it allows you to use template literals. Template literals are enclosed by backticks, and they allow you to embed expressions inside a string. Here is an example:
letname="John";letstr=`Hello${name}`;console.log(str);// Output: "Hello John"
- Template literals are useful when you want to create a string that contains variables or expressions.
- You can also use backticks to create multi-line strings:
letstr=`This is amulti-line string`;
- This is useful when you want to create a string that spans multiple lines.
- You can also use backticks to create strings that contain single quotes or double quotes:
letstr1=`This string contains a single quote (')`;letstr2=`This string contains a double quote (")`;
- This is useful when you want to create a string that contains single quotes or double quotes.
- You can also use backticks to create strings that contain backticks:
letstr=`This string contains a backtick (\`)`;
- This is useful when you want to create a string that contains backticks.
Strings have many built-in methods that allow you to perform operations on them. Here are some common string methods in alphabetical order:
The
charAt()
method returns the character at the specified index in a string. The index of the first character is 0, the index of the second character is 1, and so on. If the index is greater than or equal to the length of the string,charAt()
returns an empty string.letstr="Hello World";console.log(str.charAt(0));// Output: "H"console.log(str.charAt(1));// Output: "e"console.log(str.charAt(2));// Output: "l"console.log(str.charAt(3));// Output: "l"console.log(str.charAt(4));// Output: "o"console.log(str.charAt(10));// Output: "d"console.log(str.charAt(11));// Output: ""
The
charCodeAt()
method returns the Unicode value of the character at the specified index in a string. The index of the first character is 0, the index of the second character is 1, and so on. If the index is greater than or equal to the length of the string,charCodeAt()
returns NaN.letstr="Hello World";console.log(str.charCodeAt(0));// Output: 72console.log(str.charCodeAt(1));// Output: 101console.log(str.charCodeAt(2));// Output: 108console.log(str.charCodeAt(3));// Output: 108console.log(str.charCodeAt(4));// Output: 111console.log(str.charCodeAt(10));// Output: 100console.log(str.charCodeAt(11));// Output: NaN
The
concat()
method combines two or more strings and returns a new string. This method does not change the existing strings, but instead returns a new string.letstr1="Hello";letstr2="World";console.log(str1.concat(" ",str2));// Output: "Hello World"
The
endsWith()
method determines whether a string ends with the characters of a specified string. This method returns true if the string ends with the specified string, and false otherwise.letstr="Hello World";console.log(str.endsWith("World"));// Output: trueconsole.log(str.endsWith("World!"));// Output: false
The
includes()
method determines whether a string contains the characters of a specified string. This method returns true if the string contains the specified string, and false otherwise.letstr="Hello World";console.log(str.includes("World"));// Output: trueconsole.log(str.includes("World!"));// Output: false
The
indexOf()
method returns the index of the first occurrence of a specified value in a string. This method returns -1 if the value is not found.letstr="Hello World";console.log(str.indexOf("World"));// Output: 6console.log(str.indexOf("World!"));// Output: -1
The
lastIndexOf()
method returns the index of the last occurrence of a specified value in a string. This method returns -1 if the value is not found.letstr="Hello World";console.log(str.lastIndexOf("World"));// Output: 6console.log(str.lastIndexOf("World!"));// Output: -1
The
match()
method searches a string for a match against a regular expression, and returns the matches, as an Array object.constparagraph="The quick brown fox jumps over the lazy dog. It barked.";constregex=/[A-Z]/g;constfound=paragraph.match(regex);console.log(found);// Expected output: Array ["T", "I"]
The
matchAll()
method returns an iterator of all results matching a string against a regular expression, including capturing groups.constregexp=/t(e)(st(\d?))/g;conststr="test1test2";constfound=str.matchAll(regexp);constarray=[...found];console.log(array[0]);// Expected output: Array ["test1", "e", "st1", "1"]console.log(array[1]);// Expected output: Array ["test2", "e", "st2", "2"]
The
padStart()
method pads the current string with another string until the resulting string reaches the given length. The padding is applied from the start of the current string.letstr="Hello World";console.log(str.padStart(15," "));// Output: " Hello World"console.log(str.padStart(15,"Hello "));// Output: "Hello Hello World"console.log(str.padStart(15,"*"));// Output: "---**Hello World"
The
padEnd()
method pads the current string with another string until the resulting string reaches the given length. The padding is applied from the end of the current string.letstr="Hello World";console.log(str.padEnd(15," "));// Output: "Hello World "console.log(str.padEnd(15," World"));// Output: "Hello World World"console.log(str.padEnd(15,"*"));// Output: "Hello World---**"
The
repeat()
method returns a new string with a specified number of copies of the string it was called on.letstr="Hello World";console.log(str.repeat(3));// Output: "Hello WorldHello WorldHello World"
The
replace()
method searches a string for a specified value, or a regular expression, and returns a new string where the specified values are replaced.letstr="Hello World";console.log(str.replace("World","World!"));// Output: "Hello World!"
The
replaceAll()
method returns a new string with all matches of a pattern replaced by a replacement.letstr="Hello World";console.log(str.replaceAll("World","World!"));// Output: "Hello World!"
The
search()
method searches a string for a specified value, or regular expression, and returns the position of the match. The search value can be string or a regular expression pattern. This method returns -1 if no match is found.letstr="Hello World";console.log(str.search("World"));// Output: 6console.log(str.search("World!"));// Output: -1
The
slice()
method extracts a section of a string and returns it as a new string, without modifying the original string. This method takes two parameters: the start index and the end index (end index is not included).letstr="Hello World";console.log(str.slice(0,5));// Output: "Hello"console.log(str.slice(6,11));// Output: "World"
Using
padStart()
withslice()
to mask a string:constfullNumber="2034399002125581";constlast4Digits=fullNumber.slice(-4);// => "5581"constmaskedNumber=last4Digits.padStart(fullNumber.length,"*");console.log(maskedNumber);// Expected output: "---5581"
Using
padEnd()
withslice()
to mask a string:constfullNumber="2034399002125581";constlast4Digits=fullNumber.slice(-4);// => "5581"constmaskedNumber=last4Digits.padEnd(fullNumber.length,"*");console.log(maskedNumber);// Expected output: "5581---
The
split()
method splits a string into an array of substrings, and returns the new array. This method takes two parameters: the separator and the limit.letstr="Hello World";console.log(str.split(" "));// Output: ["Hello", "World"]console.log(str.split(" ",1));// Output: ["Hello"]
The
startsWith()
method determines whether a string begins with the characters of a specified string. This method returns true if the string begins with the specified string, and false otherwise.letstr="Hello World";console.log(str.startsWith("Hello"));// Output: trueconsole.log(str.startsWith("Hello!"));// Output: false
The
substr()
method returns a portion of the string, starting at the specified index and extending for a given number of characters afterwards. This method takes two parameters: the start index and the length.letstr="Hello World";console.log(str.substr(0,5));// Output: "Hello"console.log(str.substr(6,5));// Output: "World"
The
substring()
method returns a portion of the string, starting at the specified index and extending for a given number of characters afterwards. This method takes two parameters: thestart index
and theend index
(end index is not included).letstr="Hello World";console.log(str.substring(0,5));// Output: "Hello"console.log(str.substring(6,11));// Output: "World"
The
toLowerCase()
method converts a string to lowercase letters.letstr="Hello World";console.log(str.toLowerCase());// Output: "hello world"
The
toUpperCase()
method converts a string to uppercase letters.letstr="Hello World";console.log(str.toUpperCase());// Output: "HELLO WORLD"
The
trim()
method removes whitespace from both ends of a string.letstr=" Hello World ";console.log(str.trim());// Output: "Hello World"
The
trimStart()
method removes whitespace from the beginning of a string.letstr=" Hello World ";console.log(str.trimStart());// Output: "Hello World "
The
trimEnd()
method removes whitespace from the end of a string.letstr=" Hello World ";console.log(str.trimEnd());// Output: " Hello World"
In JavaScript, there are multiple ways to declare an object. Here are some common methods:
The most common way to create an object in JavaScript is to use an object literal. This is simply a comma-separated list of name-value pairs inside curly braces.
constperson={name:"John",age:30,address:{street:"123 Main St",city:"Anytown",state:"CA",zip:"12345",},};
Another way to create an object is to use the Object constructor. This method creates an empty object, which you can then add properties and methods to using dot notation.
constperson=newObject();person.name="John";person.age=30;person.address={street:"123 Main St",city:"Anytown",state:"CA",zip:"12345",};
The Object.create() method creates a new object, using an existing object as the prototype. This method allows you to create an object with a specific prototype, which can be useful for inheritance.
constpersonProto={greeting:function(){console.log("Hello, my name is "+this.name);},};constperson=Object.create(personProto);person.name="John";person.age=30;
Prototype chain
is a feature of JavaScript that allows objects to inherit properties and methods from other objects.In JavaScript, every object has a
prototype
object, which is essentially another object from which the first object inherits properties and methods.When a property or method is called on an object, JavaScript first looks for that property or method on the object itself.
If the property or method is not found, JavaScript then looks for it on the object's prototype.
If it is not found there, JavaScript looks on the prototype's prototype, and so on, creating a
chain of objects
until the property or method is found or there is no further prototype to search.Here is an example of prototype chain in #"auto" data-snippet-clipboard-copy-content="// Create an objectconst myObj = { a: 1,};// Create another object that inherits from myObjconst myOtherObj = Object.create(myObj);myOtherObj.b = 2;// Add a property to myObjmyObj.c = 3;// Call a method on myOtherObjconsole.log(myOtherObj.a); // Output: 1// Call a method on myOtherObj that is not defined on itconsole.log(myOtherObj.c); // Output: 3">
// Create an objectconstmyObj={a:1,};// Create another object that inherits from myObjconstmyOtherObj=Object.create(myObj);myOtherObj.b=2;// Add a property to myObjmyObj.c=3;// Call a method on myOtherObjconsole.log(myOtherObj.a);// Output: 1// Call a method on myOtherObj that is not defined on itconsole.log(myOtherObj.c);// Output: 3
In this example, we first create an objectmyObj
with a propertya
equal to1
. We then create another objectmyOtherObj
usingObject.create(myObj)
, which setsmyOtherObj
's prototype tomyObj
. We also givemyOtherObj
its own propertyb
equal to2
.
Next, we add a propertyc
tomyObj
object. This means thatc
is now a property ofmyObj
, and therefore all objects that inherit frommyObj
will also have access toc
.
Finally, we callconsole.log(myOtherObj.a)
, which logs the value ofmyOtherObj.a
to the console. SincemyOtherObj
inherits frommyObj
, it has access to thea
property defined onmyObj
, so the output is1
. We then callconsole.log(myOtherObj.c)
, which logs the value ofmyOtherObj.c
to the console.myOtherObj
does not have its ownc
property, so JavaScript looks forc
onmyOtherObj
's prototype object (which ismyObj
), and finds it there. Therefore, the output is3
.
This example demonstrates the basics of prototype chain in JavaScript. By using prototype inheritance, we can create complex object hierarchies and easily share properties and methods between objects.
In JavaScript, a function constructor is a function that is used to create objects.
A function constructor is defined using the
function
keyword, and it is conventionally named with an initialuppercase
letter to differentiate it from regular functions.The function constructor is called with the
new
keyword to create new objects.Here's an example:
// Function constructorfunctionPerson(name,age){this.name=name;this.age=age;}// Creating objects using the function constructorconstperson1=newPerson("John",30);constperson2=newPerson("Jane",25);// Accessing the properties of the objectsconsole.log(person1.name);// Output: "John"console.log(person2.age);// Output: 25
In the example above, we define a function constructor
Person
that takes two parameters,name
andage
. When the function constructor is called with thenew
keyword, it creates a new object and sets thename
andage
properties on the object using thethis
keyword.We create two objects
person1
andperson2
using thePerson
function constructor and access their properties using dot notation.Function constructors can be useful for creating multiple objects with similar properties and methods. By defining a function constructor, we can create objects with the same set of properties and methods without duplicating code. We can also add methods to the prototype object of the function constructor to allow all objects created by the constructor to inherit those methods.
In JavaScript, you can add methods to a function constructor by adding them to the
prototype
property of the constructor function.Here's an example:
// Function constructorfunctionPerson(name,age){this.name=name;this.age=age;}// Adding a method to the prototype object of the function constructorPerson.prototype.greet=function(){console.log("Hello, my name is "+this.name);};// Creating an object using the function constructorconstperson1=newPerson("John",30);// Calling the method added to the prototype objectperson1.greet();// Output: "Hello, my name is John"
In this example, we define a function constructor
Person
and add agreet
method to its prototype object. Thegreet
method uses thethis
keyword to refer to the current object and logs a greeting to the console.When we create a new object
person1
using thePerson
function constructor, the[[Prototype]]
of the object is set to thePerson.prototype
object, so thegreet
method is available on the object. We call thegreet
method onperson1
, and it logs a greeting to the console.Adding methods to the prototype object of a function constructor allows all objects created by the constructor to inherit those methods. This can be more memory-efficient than adding methods directly to each object, as the methods are shared among all objects created by the constructor.
When a function constructor is called without the
new
keyword in JavaScript, it does not create a new object to serve as thethis
value for the function. Instead, the function will execute in the global context (i.e., thewindow
object in the browser or theglobal
object in Node.js) and any properties or methods that are defined within the function will be added to the global object.For example, consider the following function constructor:
functionPerson(name,age){this.name=name;this.age=age;}
If you call this constructor with the
new
keyword like this:constperson1=newPerson("John",30);
A new object will be created and
this
will refer to that object.person1
will be an instance of thePerson
object with its ownname
andage
properties.However, if you call the constructor without the
new
keyword like this:constperson2=Person("Jane",25);
The function will execute in the global context and
this
will refer to the global object. Thename
andage
properties will be added to the global object instead of a new object.person2
will be undefined, since the function does not return anything explicitly.It's generally considered a best practice to always use the
new
keyword when calling function constructors in JavaScript to avoid unintended side effects and to ensure that thethis
value is set correctly.
To ensure that the new keyword is used when calling a function constructor in JavaScript, you can use the following technique:
You can use the
new.target
meta property to check if the new keyword is used when calling a function constructor in JavaScript.The
new.target
property is available inside a function constructor and its value will be a reference to the constructor function that was called with thenew
keyword.If the function is called without the
new
keyword,new.target
will beundefined
.Here's an example of how you can use
new.target
to check if thenew
keyword is used:functionPerson(name,age){// Check if the function is called with the new keywordif(!new.target){returnnewPerson(name,age);}// Otherwise, continue with the normal constructor logicthis.name=name;this.age=age;}
Create a
Car
function constructor that takes three parameters:make
,model
, andyear
.The function constructor should set these properties on the object created by it.
Add a method to the Car function constructor's prototype object called
start()
. This method should log a message to the console indicating that the car has started.Add a method to the Car function constructor's prototype object called
stop()
. This method should log a message to the console indicating that the car has stopped.Create two Car objects and call their
start()
andstop()
methods.Here's some example code to get you started:
constcar1=newCar("Toyota","Corolla",2020);constcar2=newCar("Honda","Civic",2019);car1.start();// Output: "The Toyota Corolla has started."car2.start();// Output: "The Honda Civic has started."
Click to show/hide solution
// Solution:functionCar(make,model,year){this.make=make;this.model=model;this.year=year;}Car.prototype.start=function(){console.log(`The${this.make}${this.model} has started.`);};Car.prototype.stop=function(){console.log(`The${this.make}${this.model} has stopped.`);};constcar1=newCar("Toyota","Corolla",2020);constcar2=newCar("Honda","Civic",2019);car1.start();// Output: "The Toyota Corolla has started."car2.start();// Output: "The Honda Civic has started."car1.stop();// Output: "The Toyota Corolla has stopped."car2.stop();// Output: "The Honda Civic has stopped."
Create a
BankAccount
function constructor that takes one parameter:balance
.The function constructor should set the balance property on the object created by it.
Add three methods to the BankAccount function constructor's prototype object:
deposit()
,withdraw()
, andgetBalance()
.The
deposit()
method should take a parameteramount
and add it to thebalance
property.The
withdraw()
method should take a parameteramount
and subtract it from thebalance
property.The
getBalance()
method shouldreturn
the currentbalance
.Create a
BankAccount
object and call itsdeposit()
,withdraw()
, andgetBalance()
methods.Here's some example code to get you started:
constaccount=newBankAccount(1000);console.log(account.getBalance());// Output: 1000account.deposit(500);console.log(account.getBalance());// Output: 1500account.withdraw(200);console.log(account.getBalance());// Output: 1300
Click to show/hide solution
// Solution:functionBankAccount(balance){this.balance=balance;}BankAccount.prototype.deposit=function(amount){this.balance+=amount;};BankAccount.prototype.withdraw=function(amount){if(amount>this.balance){console.log("Insufficient funds.");return;}this.balance-=amount;};BankAccount.prototype.getBalance=function(){returnthis.balance;};constaccount=newBankAccount(1000);console.log(account.getBalance());// Output: 1000account.deposit(500);console.log(account.getBalance());// Output: 1500account.withdraw(200);console.log(account.getBalance());// Output: 1300account.withdraw(2000);// Output: "Insufficient funds."
ES6 introduced the class syntax, which provides a more traditional object-oriented programming approach to creating objects.
classPerson{constructor(name,age){this.name=name;this.age=age;}greeting(){console.log("Hello, my name is "+this.name);}}constperson=newPerson("John",30);
These are some of the common ways to declare objects in JavaScript. Depending on your use case, one method may be more appropriate than the others.
Convert the
Car
function constructor from the previous exercise to an ES6 class.Click to show/hide solution
classCar{// Your code hereconstructor(make,model,year){this.make=make;this.model=model;this.year=year;}start(){console.log(`The${this.make}${this.model} has started.`);}stop(){console.log(`The${this.make}${this.model} has stopped.`);}}
Create two
Car
objects and call theirstart()
andstop()
methods.Click to show/hide solution
// Solution:constcar1=newCar("Toyota","Corolla",2020);constcar2=newCar("Honda","Civic",2019);car1.start();// Output: "The Toyota Corolla has started."car2.start();// Output: "The Honda Civic has started."car1.stop();// Output: "The Toyota Corolla has stopped."car2.stop();// Output: "The Honda Civic has stopped."
Convert the
BankAccount
function constructor from the previous exercise to an ES6 class.Click to show/hide solution
// Solution:classBankAccount{constructor(balance){this.balance=balance;}deposit(amount){this.balance+=amount;}withdraw(amount){if(amount>this.balance){console.log("Insufficient funds.");return;}this.balance-=amount;}getBalance(){returnthis.balance;}}
Create a
BankAccount
object and call itsdeposit()
,withdraw()
, andgetBalance()
methods.Click to show/hide solution
// Solution:constaccount=newBankAccount(1000);console.log(account.getBalance());// Output: 1000account.deposit(500);console.log(account.getBalance());// Output: 1500account.withdraw(200);console.log(account.getBalance());// Output: 1300account.withdraw(2000);// Output: "Insufficient funds."
In JavaScript, objects are non-primitive data types and are stored in the heap. When an object is assigned to a variable or passed as an argument to a function, a reference to its memory location is copied, not a copy of its value.
There are two ways to create a copy of an object:
Shallow Copy
Deep Copy
Shallow copy creates a new object that shares the same memory references as the original object for its properties.
This means that if the property value is an object, the new object will reference the same memory location as the original object.
Shallow copy is achieved through the
Object.assign()
method or thespread operator (ES6)
.Here's an example of shallow copying an object using
Object.assign()
:letoriginalObj={name:"John",age:30,address:{street:"123 Main St",city:"Anytown",state:"CA",},};letnewObj=Object.assign({},originalObj);console.log(newObj);// Output: {name: "John", age: 30, address: {street: "123 Main St", city: "Anytown", state: "CA"}}console.log(originalObj===newObj);// Output: falseconsole.log(originalObj.address===newObj.address);// Output: true
As you can see, the
newObj
is a shallow copy oforiginalObj
.newObj
has its own memory space, but the "address
" property ofnewObj
references the same memory location as the "address
" property oforiginalObj
.Here's an example of
shallow
copying an object using thespread operator
:letoriginalObj={name:"John",age:30,address:{street:"123 Main St",city:"Anytown",state:"CA",},};letnewObj={ ...originalObj};console.log(newObj);// Output: {name: "John", age: 30, address: {street: "123 Main St", city: "Anytown", state: "CA"}}console.log(originalObj===newObj);// Output: falseconsole.log(originalObj.address===newObj.address);// Output: true
As you can see, the
newObj
is a shallow copy oforiginalObj
.newObj
has its own memory space, but the "address
" property ofnewObj
references the same memory location as the "address
" property of originalObj.
Deep copy
, on the other hand, creates a completely new object with its own memory space for all properties, including those that are objects themselves.This ensures that changes made to the copied object
do not affect
the original object or any other copies of the object.There are different ways to achieve
deep copy
, one of which is using theJSON.parse()
andJSON.stringify()
methods.The
JSON.parse()
andJSON.stringify()
methods can be used to copy an object, creating a deep copy. This method works by first serializing the original object to a JSON string usingJSON.stringify()
, and then deserializing the JSON string back into an object usingJSON.parse()
.Here's an example of deep copying an object using
JSON.parse()
andJSON.stringify()
:letoriginalObj={name:"John",age:30,address:{street:"123 Main St",city:"Anytown",state:"CA",},};letnewObj=JSON.parse(JSON.stringify(originalObj));console.log(newObj);// Output: {name: "John", age: 30, address: {street: "123 Main St", city: "Anytown", state: "CA"}}console.log(originalObj===newObj);// Output: falseconsole.log(originalObj.address===newObj.address);// Output: false
As you can see,
newObj
is a deep copy oforiginalObj
.newObj
has its own memory space for all properties, including the "address
" property. Therefore, changes made tonewObj
will not affect theoriginalObj
or any other copies of the object.***Note that using
JSON.parse()
andJSON.stringify()
may not work for all cases, such as when the object containsfunctions
orsymbols
. In those cases, other methods of deep copying may be necessary.
The spread operator is a new addition to the set of operators in JavaScript ES6. It takes in an iterable (e.g an array) and expands it into individual elements.
The spread operator is commonly used to make shallow copies of JS objects. Using this operator makes the code concise and enhances its readability.
Examples with Arrays:
constarr=[1,2,3];console.log(...arr);
constarr=[1,2,3];constarr2=[4,5,6];constarr3=[...arr, ...arr2];console.log(arr3);
constarr=[1,2,3];constarr2=[...arr,4,5,6];console.log(arr2);
- Example with Objects:
constperson={name:"John",age:30};constclone={ ...person};console.log(clone);
constperson={name:"John",age:30};constclone={ ...person,location:"USA"};console.log(clone);
constperson={name:"John",age:30};constclone={location:"USA", ...person};console.log(clone);
constperson={name:"John",age:30};constclone={ ...person,age:40};console.log(clone);
constperson={firstName:"John",lastName:"Doe",age:30};constaddress={city:"New York",state:"NY",zip:"10001"};constuser={ ...person, ...address};console.log(user);
In JavaScript, there are several ways to declare functions. Here are some of the most common ones:
Function Declaration
: This is the most common way to declare a function. It uses the "function
" keyword followed by the function name, parameters, and function body.functionfunctionName(parameter1,parameter2){// function bodyreturnsomething;}
Function Expression
: In this method, you declare a function as a variable andassign
it a value using the "function" keyword.varfunctionName=function(parameter1,parameter2){// function bodyreturnsomething;};
Arrow Function
: This is a new way to declare functions in ES6 (ECMAScript 2015). Arrow functions are shorter and have a more concise syntax compared to regular functions.varfunctionName=(parameter1,parameter2)=>{// function bodyreturnsomething;};varfunctionName=parameter1=>{// function body to do something and then return somethingreturnsomething;};varfunctionName=(parameter1,parameter2)=>returnsomething;varfunctionName=parameter1=>returnsomething;
Method Definition
: This is used to declare a function as aproperty
of anobject
.varobj={functionName:function(parameter1,parameter2){// function bodyreturnsomething;},};
Some examples of different types of functions in JavaScript based on their behavior and side-effects:
Pure function:
A pure function is a function that doesn't modify any data outside of its own scope and always returns the same result for the same input. Here is an example:
functionadd(a,b){returna+b;}
Impure function:
An impure function is a function that modifies data outside of its own scope or has side effects. Here is an example:
letcount=0;functionincrementCount(){count++;}
Higher-order function:
A higher-order function is a function that takes one or more functions as arguments or returns a function as its result. Here is an example:
functionmultiplyBy(num){returnfunction(x){returnx*num;};}constdouble=multiplyBy(2);console.log(double(5));// Output: 10
Recursive function:
A recursive function is a function that calls itself until a certain condition is met. Here is an example:
functionfactorial(num){if(num===0||num===1){return1;}returnnum*factorial(num-1);}console.log(factorial(5));// Output: 120
Arrow function:
An arrow function is a more concise syntax for writing a function in JavaScript. Here is an example:
constadd=(a,b)=>{returna+b;};console.log(add(2,3));// Output: 5
Generator function:
A generator function is a function that can pause its execution and return multiple values one at a time. Here is an example:
function*processNumbers(numbers){for(constnumofnumbers){// Perform some operation on numconstresult=num*2;// Pause the iteration and yield the resultyieldresult;}}functiondoSomething(){console.log("Doing something...");}constnumbers=[1,2,3,4,5];constgen=processNumbers(numbers);// Iterate over the generator and pause after each resultconsole.log(gen.next().value);// 2doSomething();console.log(gen.next().value);// 4doSomething();console.log(gen.next().value);// 6doSomething();console.log(gen.next().value);// 8doSomething();console.log(gen.next().value);// 10
In javaScript functions are considered first-class objects, which means they can be treated like any other value or object. This means that:
Functions can be assigned to variables:
constadd=function(a,b){returna+b;};console.log(add(2,3));// 5
Functions can be returned as values from functions:
functionmultiply(x,y){returnx*y;}// multiply 3,4,5, by 2multiply(2,3);//6multiply(2,4);// 8multiply(2,5);// 10// multiply 3,4,5, by 3multiply(3,3);// 9multiply(3,4);// 12multiply(3,5);// 15
functionmultiplyByTwo(x){returnx*2;}functionmultiplyByThree(x){returnx*3;}functionmultiplyByFour(x){returnx*4;}
functionmakeMultiplier(factor){returnfunction(number){returnnumber*factor;};}constmultiplyByTwo=makeMultiplier(2);// multiply 3,4,5, by 2console.log(multiplyByTwo(3));// 6console.log(multiplyByTwo(4));// 8console.log(multiplyByTwo(5));// 10constmultiplyByThree=makeMultiplier(3);// multiply 3,4,5, by 3console.log(multiplyByThree(3));// 9console.log(multiplyByThree(4));// 12console.log(multiplyByThree(5));// 15
- When a function returns another function as its result, it is called a "
closure
". - A
closure
is a function that has access to variables in its outer (enclosing) scope, even after the outer function has returned.
- When a function returns another function as its result, it is called a "
Functions can be stored in data structures like arrays or objects:
constoperations={add:function(a,b){returna+b;},subtract:function(a,b){returna-b;},multiply:function(a,b){returna*b;},divide:function(a,b){returna/b;},};console.log(operations.add(2,3));// 5console.log(operations.divide(10,2));// 5
Functions can be passed as arguments to other functions:
functiongreeting(name){console.log(`Hello,${name}!`);}functiongreetingWrapper(fn,name){console.log("Preparing to greet...");fn(name);console.log("Greeting complete.");}greetingWrapper(greeting,"Alice");// Output:// Preparing to greet...// Hello, Alice!// Greeting complete.
- JS is
single-threaded
programming language, which means it can only execute one task at a time. - This is because it has only one
call stack
, which is responsible for executing the code. - This means that if a function is currently executing, no other code can run until the call stack is clear.
- This can be a problem if a function takes a long time to execute, because it will block the call stack and prevent other code from running. This is known as
blocking code
. - Blocking code can be a problem in JavaScript because it can make the user interface unresponsive.
- For example, if a function takes a long time to execute, the user will not be able to interact with the page until the function is finished executing.
- This can be a problem if the function is doing something that is not related to the user interface, such as fetching data from a server or performing a complex calculation.
- To solve this problem, JavaScript developers often use
asynchronous programming techniques
, such ascallbacks
,promises
, andasync/await
, to allow the application to continue running while it waits for long-running tasks to complete. - This allows the user interface to remain responsive while the application is performing long-running tasks.
- A callback is a function that is passed as an argument to another function
- And is executed after some operation has been completed
Example to show the problem without callback function:
// breaking the DRY principleconstnums=[1,2,3,4,5,6,7,8,9,10];console.log(nums);functioncopyArrayAndMultiplyByTwo(array){constoutput=[];for(leti=0;i<array.length;i++){output.push(array[i]*2);}returnoutput;}constnumbers1=copyArrayAndMultiplyByTwo(nums);// [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]functioncopyArrayAndDivideByTwo(array){constoutput=[];for(leti=0;i<array.length;i++){output.push(array[i]/2);}returnoutput;}constnumbers2=copyArrayAndDivideByTwo(nums);// [0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5]functioncopyArrayAndAddTwo(array){constoutput=[];for(leti=0;i<array.length;i++){output.push(array[i]+2);}returnoutput;}constnumbers3=copyArrayAndAddTwo(nums);// [3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
The solution using callback function
// using the callback functions:functionmultiplyByTwo(number){returnnumber*2;}functiondivideByTwo(number){returnnumber/2;}functionaddTwo(number){returnnumber+2;}functioncopyArray(array,instructions){constnewArray=[];array.forEach((element)=>{newArray.push(instructions(element));});returnnewArray;}constnumbers=[1,2,3,4,5,6,7,8,9,10];console.log(numbers);// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]constnewNumbers=copyArray(numbers,multiplyByTwo);console.log(newNumbers);// [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]constnewNumbers2=copyArray(numbers,divideByTwo);console.log(newNumbers2);// [0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5]constnewNumbers3=copyArray(numbers,addTwo);console.log(newNumbers3);// [3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
- You don't need to create a new function for each operation
- You can pass a annonymous/arrow function as an argument to another function
// Passing annonymous function as arguments to another functionconstnewNumbers4=copyArray(numbers,function(number){returnnumber+2;});console.log(newNumbers4);// [3, 4, 5, 6, 7, 8, 9, 10, 11, 12]// Passing arrow function as arguments to another functionconstnewNumbers5=copyArray(numbers,(number)=>number+2);console.log(newNumbers5);// [3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
Example to show how to use callback function to handle events in the browser
constbutton=document.querySelector("button");functionhandleClick(event){console.log("Button clicked!");}button.addEventListener("click",handleClick);
In asynchronous programming, a function may take some time to complete its operation. This can cause the program to pause or become unresponsive.
Callbacks provide a way to avoid this by allowing the program to continue executing while the asynchronous function is running in the background.
functiongetData(callback){setTimeout(()=>{constdata={name:"John Doe",age:30};callback(data);},1000);}functionprocessData(data){console.log(`Name:${data.name}, Age:${data.age}`);}getData(processData);// Output after 1 second: Name: John Doe, Age: 30
The above example shows how callbacks can be used to handle asynchronous operations in JavaScript.
The
getData()
function takes a callback function as an argument and executes it after one second.The
processData()
function is passed as an argument to thegetData()
function and is executed after one second.The
processData()
function receives the data object as an argument and logs it to the console.The
getData()
function is called with theprocessData()
function as an argument.The
getData()
function executes theprocessData()
function after one second and passes the data object as an argument.
Higher order function is a function that takes a function as an argument
functioncopyArray(array,instructions){constnewArray=[];array.forEach((element)=>{newArray.push(instructions(element));});returnnewArray;}functionmultiplyByTwo(number){returnnumber*2;}constnumbers=[1,2,3,4,5,6,7,8,9,10];// Using the higher-order function to pass a function as an argumentconstnewNumbers=copyArray(numbers,multiplyByTwo);console.log(newNumbers);// [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
Or a function that returns a function as a result
// example of a function that returns a function as a result:functioncreateAdder(num){returnfunction(x){returnx+num;};}constaddTwo=createAdder(2);console.log(addTwo(10));// 12constaddFive=createAdder(5);console.log(addFive(10));// 15
Examples of built-in higher-order functions in JavaScript:
Array.map()
: Returns a new array with the same number of elements as the original array, where each element is transformed according to a function that is passed as an argument.constnumbers=[1,2,3,4,5];constdoubledNumbers=numbers.map(function(num){returnnum*2;});console.log(doubledNumbers);// Output: [2, 4, 6, 8, 10]
Array.filter()
: Returns a new array with only the elements that pass a test implemented by a function that is passed as an argument.constnumbers=[1,2,3,4,5];constevenNumbers=numbers.filter(function(num){returnnum%2===0;});console.log(evenNumbers);// Output: [2, 4]
Array.reduce()
:
The goal of the
reduce
method is to reduce the array to a single value by applying a function to each element in that array (from left to right).The function that is passed to
reduce()
takes two arguments: anaccumulator
and thecurrent value
in the array.The function returns the updated
accumulator
, which is used as theaccumulator
for the next iteration.The function is called once for each element in the array.
constnumbers=[1,2,3,4,5];constsumOfNumbers=numbers.reduce(function(acc,num){returnacc+num;},0);console.log(sumOfNumbers);// Output: 15
These built-in higher-order functions make it easy to write concise and expressive code that manipulates arrays.
Promises
are used to handle asynchronous operations in JavaScript.- An
asynchronous operation
is one that doesn't block the execution of the program while it's running, but instead runs in the background andnotifies
the program when it's finished. - A
Promise
is an object that represents the eventual completion or failure of an asynchronous operation. Promises
returns an object to which you attach callbacks, instead of passing callbacks into a function.- A
Promise
is in one of these states:pending
: initial state, neither fulfilled nor rejected.fulfilled
: meaning that the operation was completed successfully.rejected
: meaning that the operation failed.
// example of creating a promise using the Promise constructor:letpromise=newPromise(function(resolve,reject){// executor (the producing code, "singer")});
The function passed to
new Promise
is called theexecutor
.When new
Promise
is created, theexecutor
runs automatically. It contains the producing code which should eventually produce the result.Its arguments
resolve
andreject
are callbacks provided by JavaScript itself.When the
executor
obtains the result, it should call one of these callbacks:resolve(value)
— if the job finished successfully, with resultvalue
.reject(error)
— if an error occurred,error
is the error object.
So to summarize: the
executor
runs automatically and attempts to perform a job.When it is finished with the attempt, it calls
resolve
if it was successful orreject
if there was an error.constmyPromise=newPromise((resolve,reject)=>{// Do some async operation here...if(/* some condition */){resolve(/* some value */);// Fulfill the promise}else{reject(/* some error */);// Reject the promise}});myPromise.then(result=>{// The promise was fulfilled}).catch(error=>{// The promise was rejected});
The
promise
object returned by thenew Promise
constructor has these internal properties:Here’s an example of a
promise
constructor and a simpleexecutor
function with “producing code” that takes time (viasetTimeout
):letpromise=newPromise(function(resolve,reject){// the function is executed automatically when the promise is constructed// after 5 second signal that the job is done with the result "done"setTimeout(()=>resolve("done"),5000);});console.log(promise);// Promise { <pending> }
We can see two things by running the code above:
The
executor
is called automatically and immediately (bynew Promise
).The
executor
receives two arguments:resolve
andreject
. These functions are pre-defined by the JavaScript engine, so we don’t need to create them. We should only call one of them when ready.
After 5 seconds of “processing”, the
executor
callsresolve("done")
to produce the result. This changes thestate
of thepromise
object:- That was an example of a successful job completion, a
“fulfilled promise”
.
And now an example of the
executor
rejecting thepromise
with anerror
:letpromise=newPromise(function(resolve,reject){// after 5 seconds signal that the job is finished with an errorsetTimeout(()=>reject(newError("Whoops!")),5000);});console.log(promise);// Promise { <pending> }
- The call to
reject(...)
moves the promise object to "rejected" state:
- A
Promise
object serves as a link between theexecutor
(the “producing code”) and theconsuming functions
, which will receive theresult
orerror
. Consuming functions
can be registered (subscribed) using methods.then
,.catch
and.finally
.
The most important, fundamental one is .then.
The syntax is:
promise.then(function(result){/* handle a successful result */},function(error){/* handle an error */});
The first argument of
.then
is a function that runs when the promise isfulfilled
, and receives theresult
.The second argument of
.then
is a function that runs when the promise isrejected
, and receives theerror
.For instance, here’s a reaction to a
successfully
resolved promise:letpromise=newPromise(function(resolve,reject){setTimeout(()=>resolve("done!"),1000);});// resolve runs the first function in .thenpromise.then((result)=>alert(result),// shows "done!" after 1 second(error)=>alert(error)// doesn't run);
And in the case of a
rejection
, the second one:letpromise=newPromise(function(resolve,reject){setTimeout(()=>reject(newError("Whoops!")),1000);});// reject runs the second function in .thenpromise.then((result)=>alert(result),// doesn't run(error)=>alert(error)// shows "Error: Whoops!" after 1 second);
If we’re interested only in successful completions, then we can provide only one function argument to
.then
:letpromise=newPromise((resolve)=>{setTimeout(()=>resolve("done!"),1000);});promise.then(alert);// shows "done!" after 1 second
If we’re interested only in
errors
, then we can usenull
as the first argument:.then(null, errorHandlingFunction)
, which is exactly the same:letpromise=newPromise((resolve,reject)=>{setTimeout(()=>reject(newError("Whoops!")),1000);});// .catch(f) is the same as promise.then(null, f)// promise.then(null, alert); // shows "Error: Whoops!" after 1 secondpromise.catch(alert);// shows "Error: Whoops!" after 1 second
The call
.catch(f)
is a complete analog of.then(null, f)
, it’s just a shorthand.
The call
.finally(f)
is similar to.then(f, f)
in the sense thatf
always runs when the promise issettled
: be itresolve
orreject
.The idea of
finally
is to set up a handler for performing cleanup/finalizing after the previous operations are complete.E.g. stopping loading indicators, closing no longer needed connections, etc.
A
finally
handler “passes through” theresult
orerror
to the next suitable handler.For instance, here the
result
is passed throughfinally
tothen
:newPromise((resolve,reject)=>{setTimeout(()=>resolve("value"),2000);}).finally(()=>alert("Promise ready"))// triggers first.then((result)=>alert(result));// <-- .then shows "value"
As you can see, the
value
returned by the firstpromise
is passed throughfinally
to the nextthen
.And here’s an example of an
error
, for us to see how it’s passed throughfinally
tocatch
:newPromise((resolve,reject)=>{thrownewError("error");}).finally(()=>alert("Promise ready"))// <-- .finally handles the error.catch((err)=>alert(err));// <-- .catch handles the error object
A
finally
handler has no arguments.In
finally
we don’t know whether thepromise
is successful or not. That’s all right, as our task is usually to perform“general”
finalizing procedures.That’s very convenient, because
finally
is not meant to process apromise result
. As said, it’s a place to dogeneric cleanup
, no matter what the outcome was.A
finally
handler also shouldn’t return anything. If it does, the returned value is silently ignored.The only
exception
to this rule is when afinally
handler throws anerror
. Then thiserror
goes to thenext handler
, instead of anyprevious outcome
.
In frontend programming,
promises
are often used fornetwork requests
.The
Fetch API
provides an interface for fetching resources.It is a more powerful and flexible replacement for
XMLHttpRequest
.For making a request and fetching a resource, use the
fetch()
method.It is a global method in Window object.
The
fetch()
method takes one mandatory argument, thepath
to the resource you want to fetch.The
fetch()
method does not directly return theJSON response body
but instead it returns apromise
that resolves with aResponse object
.The
Response object
, in turn, does not directly contain the actualJSON response
body but is instead a representation of the entireHTTP response
.So, to extract the
JSON body content
from theResponse object
, we use thejson()
method.The
json()
returns a secondpromise
thatresolves
with theresult
of parsing theresponse body text
asJSON
.json()
is a method on theresponse object
that returns apromise
to parse the body text asJSON
.fetch("https://jsonplaceholder.typicode.com/todos/1")// reads the remote data and parses it as JSON.then((response)=>response.json()).then((json)=>console.log(json));
constURL="https://dummyjson.com/products";fetch(URL)// returns a promise.then((res)=>res.json())// returns a promise.then((json)=>json)// returns a json object.then((data)=>{console.log(data.products);returndata.products;// for the next then()}).then((products)=>{console.log(products[0]);returnproducts[0];// for the next then()}).then((firstProduct)=>{console.log(firstProduct.title);});
letuser={name:"John",surname:"Smith",};letresponse=awaitfetch("/article/fetch/post/user",{method:"POST",headers:{"Content-Type":"application/json;charset=utf-8",},body:JSON.stringify(user),});letresult=awaitresponse.json();alert(result.message);
Explanation of the code:
We first create an object called
user
with two properties,name
andsurname
.letuser={name:"John",surname:"Smith",};
We then use the
fetch()
function to make aPOST
request to a URL endpoint (/article/fetch/post/user
in this case) and pass in anoptions object
that specifies themethod
,headers
, andbody
of the request.In this case, we are sending the
user
object as the requestbody
after converting it to aJSON string
usingJSON.stringify()
.letresponse=awaitfetch("/article/fetch/post/user",{method:"POST",headers:{"Content-Type":"application/json;charset=utf-8",},body:JSON.stringify(user),});
The
Content-Type header
is used to indicate the media type orformat
of the data being sent in thebody
of anHTTP request
.It informs the server about how to interpret and process the data in the
request body
.In the provided code, the
Content-Type header
is set to"application/json;charset=utf-8"
. This specifies that the body of the request contains data inJSON
format, and the character encoding used isUTF-8
.By setting the
Content-Type header
appropriately, you are providing a hint to theserver
on how to handle therequest body
.When the
server
receives therequest
, it checks theContent-Type header
to determine how to parse and interpret the incoming data.In this case, the
server
can expect the body to be inJSON
format and use appropriate parsing mechanisms to convert theJSON string
into anobject
or perform any necessary processing.In the context of the
Content-Type header
with"charset=utf-8"
, it indicates that the content of the request body is encoded using theUTF-8 character encoding
. By specifying this encoding, you ensure that theserver
correctly interprets and handles the text data encoded in UTF-8 format.The
JSON.stringify()
method is used in this code to convert theuser
object into a JSON string representation.When making an
HTTP request
, thebody
of the request typically contains the data that needs to be sent to the server. However, theHTTP protocol
itself only supports sendingtextual data
.JSON
(JavaScript Object Notation) is a popular data interchange format that is widely supported and understood by many programming languages and platforms.In order to include the
user
object as the body of theHTTP request
, it needs to be converted into a format that can be transmitted over the network.By using
JSON.stringify(user)
, theuser
object is serialized into aJSON string
, which is a textual representation of the object's data.This allows the data to be sent as the body content of the request.
letuser={name:"John",surname:"Smith",};letjsonString=JSON.stringify(user);console.log(jsonString);// '{"name":"John","surname":"Smith"}'
On the
server side
, when the request is received, the server can thenparse
theJSON string
back into an object and access the individual properties of theuser
object.This is commonly done using
JSON parsing libraries
orbuilt-in functionality
provided by the server-side programming language or framework.We then use the
json()
method on theresponse
object to parse the response body as JSON and return a JavaScript object. We store the parsed object in theresult
variable usingawait
since thejson()
method returns aPromise
.letresult=awaitresponse.json();
Finally, we display a message from the parsed JSON data using the
alert()
function. In this case, we assume that the response body has a property calledmessage
.alert(result.message);
There’s a special syntax to work with
promises
in a more comfortable fashion, called“async/await”
. It’s surprisingly easy to understand and use.The word
“async”
before a function means one simple thing: a function always returns apromise
. Other values are wrapped in a resolved promise automatically.let's check this function:
functiongetEmployeesData(){constemployees=["John","Jane","Jack"];returnnewPromise((resolve,reject)=>{if(employees.length>0){resolve("Employees exist");}else{reject("Employees does not exist");}});}// call the functiongetEmployeesData().then((reolveValue)=>console.log(reolveValue),(rejectValue)=>console.error(rejectValue));
We can rewrite the previous function as:
functiongetEmployeesData(){constemployees=["John","Jane","Jack"];if(employees.length>0){returnPromise.resolve("Employees exist");}else{returnPromise.reject("Employees does not exist");}}// call the functiongetEmployeesData().then((reolveValue)=>console.log(reolveValue),(rejectValue)=>console.error(rejectValue));
We can rewrite the previous function using
async
as:asyncfunctiongetEmployeesData(){constemployees=["John","Jane","Jack"];if(employees.length>0){return"Employees exist";}else{throw"Employees does not exist";}}// call the functiongetEmployeesData().then((reolveValue)=>console.log(reolveValue),(rejectValue)=>console.error(rejectValue));
await
is a keyword that is used inside anasync
function.await
waits for thepromise
toresolve
and returns the result.We use the
await
keyword to wait for the response to come back before moving on to the next line of codeawait
only works insideasync
functions within regular JavaScript code, however it can be used on its own with JavaScriptmodules
.Example
functiongetEmployeesData(){console.log("Start of getEmployeesData()");constURL="https://dummyjson.com/products";constresponse=fetch(URL);response.then((res)=>res.json()).then((json)=>json.products).then((products)=>products[0]).then((firstProduct)=>firstProduct.title).then((title)=>console.log(title));console.log("End of getEmployeesData()");}getEmployeesData();
The above function can be rewritten using
async
andawait
as:asyncfunctiongetEmployeesData(){console.log("Start of getEmployeesData()");constURL="https://dummyjson.com/products";constresponse=awaitfetch(URL);// wait for this promise to resolve before moving on to the next line of code.constjson=awaitresponse.json();// wait until the resolved promise is pasred to jsonconstproducts=json.products;constfirstProduct=products[0];consttitle=firstProduct.title;console.log(title);console.log("End of getEmployeesData()");}getEmployeesData();
The keyword
await
makes JavaScript wait until thatpromise
settles and returns its result.await
literallysuspends
the function execution until thepromise
settles, and thenresumes
it with thepromise
result.That doesn’t cost any CPU resources, because the
JavaScript
engine can do other jobs in the meantime: execute other scripts, handle events, etc.It’s just a more elegant syntax of getting the
promise
result thanpromise.then
. And, it’s easier to read and write.The above function can be rewritten using
try
catch
as:asyncfunctiongetEmployeesData(){console.log("Start of getEmployeesData()");constURL="https://dummyjson.com/products";try{constresponse=awaitfetch(URL);constjson=awaitresponse.json();constproducts=json.products;constfirstProduct=products[0];consttitle=firstProduct.title;console.log(title);}catch(error){console.log("error in catch",error);}console.log("End of getEmployeesData()");}
In modern browsers,
await
on top level works just fine, when we’re inside amodule
. But if the code is not inside a module, thenawait
will produce anerror
.Example:
// we assume this code runs at top level, inside a moduleletresponse=awaitfetch("/article/promise-chaining/user.json");letuser=awaitresponse.json();console.log(user);
asyncfunctionfetchData(URL){try{constresponse=awaitfetch(URL);// console.log(response);if(!response.ok){console.log(response.status);thrownewError(`HTTP error! status:${response.status}`);}constdata=awaitresponse.json();returndata;}catch(error){console.error("Error:",error);}}try{constresponse=awaitfetchData("https://dummyjson.com/products");constdata=response?.products;console.log(data);}catch(error){console.error("Error outer catch:",error);}
asyncfunctionpostJSON(URL,data){try{constresponse=awaitfetch(URL,{method:"POST",// or 'PUT'headers:{"Content-Type":"application/json",},body:JSON.stringify(data),});constresult=awaitresponse.json();console.log("Success:",result);returnresult;}catch(error){console.error("Error:",error);}}constURL="https://example.com/profile";constdata={username:"example"};constresponse=awaitpostJSON(URL,data);
- The call to
- callback hell is a situation where you have a lot of nested callbacks
loadScript("/my/script.js",function(script){loadScript("/my/script2.js",function(script){loadScript("/my/script3.js",function(script){// ...continue after all scripts are loaded});});});
- This code uses callbacks to load three scripts in sequence. Each time a script is loaded, the next script is loaded in the callback function.
- This creates a "
pyramid of doom
" structure where the code becomes nested and hard to read. It also makes error handling difficult - To solve this, we can use
Promises
andasync/await
. Here's an example of how to refactor the code using Promises and async/await:
functionloadScript(src){returnnewPromise((resolve,reject)=>{constscript=document.createElement("script");script.src=src;script.onload=()=>{document.head.appendChild(script);resolve(script);};script.onerror=()=>reject(newError(`Failed to load script${src}`));});}asyncfunctionstart(){try{constscript1=awaitloadScript("/my/script.js");console.log(`${script1.src} loaded`);constscript2=awaitloadScript("/my/script2.js");console.log(`${script2.src} loaded`);constscript3=awaitloadScript("/my/script3.js");console.log(`${script3.src} loaded`);// continue after all scripts are loaded}catch(err){console.error(err);}}start();
- This refactored code uses
Promises
to load the scripts, andasync/await
to wait for each Promise toresolve
before continuing with the next statement. - This approach makes the code much more readable and maintainable, and avoids the "
pyramid of doom
" structure of callback hell.