Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 65

Major Features of ES6

ES6, also known as ECMAScript 2015, introduced several significant


features and enhancements to the JavaScript language. Some of the
major features of ES6 include:

1. let and const Declarations: `let` and `const` provide block-scoped


variable declarations, replacing `var`. `let` allows variables to be re-
assigned while `const` creates variables whose values can't be re-
assigned.

2. Arrow Functions: Arrow functions provide a more concise syntax for


writing anonymous functions. They also lexically bind the `this` value,
which helps to prevent bugs related to scoping and context.

3. Classes: ES6 introduced a more straightforward syntax for creating


classes and working with inheritance in JavaScript, which is syntactic
sugar over the existing prototype-based inheritance.

4. Template Literals: Template literals allow for easier string


interpolation and multiline strings. They use backticks (\`) instead of
single or double quotes.

5. Destructuring Assignment: Destructuring assignment allows for


extracting values from arrays or properties from objects into distinct
variables, which makes working with complex data structures more
concise.

6. Default Parameters: Default parameter values allow you to specify


default values for function parameters, which are used when the
corresponding arguments are not passed to the function.
7. Rest and Spread Operators: The rest (`...`) and spread (`...`)
operators allow for working with variable numbers of arguments in
functions and for expanding iterables like arrays or strings into
individual elements.

8. Promises: Promises provide a cleaner and more flexible way to work


with asynchronous operations compared to callbacks. They simplify
asynchronous code by representing the eventual completion or failure of
an asynchronous operation.

9. Modules: ES6 introduced a standardized module format (`import`


and `export`), allowing JavaScript code to be organized into reusable
modules with clear dependencies.

10. Symbol Data Type: Symbols are a new primitive data type introduced
in ES6. They provide a way to create unique identifiers, which can be
used as property keys in objects.

11. Iterators and Iterables: ES6 introduced the concept of iterators and
iterables, which allow for custom iteration behavior over data structures
such as arrays, maps, and sets.

These features, among others, have significantly enhanced the


capabilities and readability of JavaScript code, making it more
expressive and easier to maintain.

Compare and contrast ES6 and ES5


ES6 (ECMAScript 2015) and ES5 (ECMAScript 5) represent two
different versions of the ECMAScript standard, the specification that
JavaScript is based on. Here's a comparison between ES6 and ES5:
1. Syntax: ES6 introduces new syntax features such as arrow functions,
template literals, destructuring assignment, and class declarations.
These syntax enhancements aim to make JavaScript code more
expressive, concise, and easier to read compared to the more verbose
syntax of ES5.

2. Block Scope: ES6 introduces block-scoped variables declared using


`let` and `const`, whereas in ES5, variables are function-scoped when
declared using `var`. This change helps in avoiding common pitfalls
related to variable hoisting and unintended variable reassignments.

3. Arrow Functions: ES6 introduces arrow functions (`=>`), which


provide a more concise syntax for defining anonymous functions and
lexically bind the `this` value, solving issues related to the traditional
function declaration's dynamic scoping of `this`.

4. Classes: ES6 introduces a more familiar class-based syntax for


defining objects and implementing inheritance, which is syntactic sugar
over the existing prototype-based inheritance in ES5.

5. Promises: ES6 introduces native support for promises, providing a


cleaner and more structured way to handle asynchronous operations
compared to the callback-based patterns commonly used in ES5.

6. Template Literals: ES6 introduces template literals, allowing for easier


string interpolation and multiline strings compared to ES5's string
concatenation.

7. Modules: ES6 introduces a standardized module system using


`import` and `export` statements, which allows for better code
organization, encapsulation, and dependency management compared to
the ad-hoc module patterns used in ES5.

8. Default Parameters and Rest Parameters: ES6 introduces default


parameter values for functions and the rest parameter syntax (`...`) for
functions, which make function declarations more flexible and concise
compared to the workarounds required in ES5.

9. Enhanced Object Literals: ES6 enhances object literals with new


syntax features such as shorthand property and method definitions,
computed property names, and method definition shorthand, making
object initialization more concise and expressive compared to ES5.

In summary, ES6 introduces significant enhancements and new features


over ES5, aimed at improving code readability, maintainability, and
developer productivity. However, ES5 remains widely used, especially in
legacy codebases and environments where ES6 support may not be
available.

Variable declaration using ‘var’ keyword


//Ex:1

function example() {
if (true) {
var x = 10;
console.log(x); // Output: 10
}
console.log(x); // Output: 10
}
example();

//ex:2
function test(){
var a = 10;
console.log("Value of 'a' inside funuction", a);
}
test();
try{
console.log("Triyng to access 'a' defined in function ")
console.log(a);
}catch(e){
console.log(e.message);
}

Let and Const Variable declaration:


-Block scoped variable declaration
The value of a constant can't be changed through reassignment using
the assignment operator, but if a constant is an object, its properties can
be added, updated, or removed.
Syntax : const name1 = value1, name2 = value2, /* …, */ nameN =
valueN;

Example:
const number = 42;

try {
number = 99;
} catch (err) {
console.log(err);
// Expected output: TypeError: invalid assignment to const 'number'
// (Note: the exact output may be browser-dependent)
}

console.log(number);

//ex:
function example() {
const z = 30;
console.log(z); // Output: 30

z = 40; // TypeError: Assignment to constant variable


}

example();

// let example
function example() {
if (true) {
let y = 20;
console.log(y); // Output: 20
}
console.log(y); // ReferenceError: y is not defined
}

example();

function test() {
{
let a = 10;
const b = 5;
}

try{
console.log("We will get error when we try to access a b")
console.log(a, b);// error will be thrown here
} catch(e) {
console.log(e.message);
}
}
test();

Main differences between let, var, and const are:

 var is function-scoped, let and const are block-scoped.


 var has hoisting, let and const do not.
 let variables can be reassigned, const variables cannot be
reassigned after initialization.

The Concept of Variable Shadowing


Variable shadowing occurs when you declare a variable with the same
name inside a local scope, effectively "hiding" the variable with the same
name in a higher scope.

Default parameters

Default function parameters allow named parameters to be


initialized with default values if no value or undefined is passed.

function multiply(a, b = 1) {
return a * b;
}
console.log(multiply(5, 2));

console.log(multiply(5));
console.log(multiply());
In JavaScript, function parameters default to undefined.
function f(x = 1, y) {
return [x, y];
}
f(); //
f(2);
Note: The first default parameter and all parameters after it will not
contribute to the function's length.

The default parameter initializers live in their own scope, which is a


parent of the scope created for the function body.

//the default parameter value does not have access to the child scope of
the function body:
functions and variables declared in the function body cannot be referred
to from default value parameter initializers; attempting to do so throws a
run-time ReferenceError.
function go() {
return ":P";
}

function f(a = go())


}
f(); // ReferenceError: go is not defined

Earlier parameters are available to later default parameters


function greet(name, greeting, message = `${greeting} ${name}`) {
return [name, greeting, message];
}
greet("David", "Hi");
greet("David", "Hi", "Happy Birthday!");

The parameter can take a default value which is a result of a function.


function date(d = today()) {
console.log(d);
}
function today() {
return (new Date()).toLocaleDateString("en-US");
}
date();
Default Parameter are Evaluatated at call time:

// The default argument is evaluated at call time. Unlike with Python (for example), a
new object is created each time the function is called.

function append(value, array = []) {


array.push(value);
return array;
}

append(1);
append(2);

Destructured parameter with default value assignment

You can use default value assignment with the destructuring


assignment syntax.

setting an empty object/array as the default value for the destructured


parameter; for example: [x = 1, y = 2] = [].

function preFilledArray([x = 1, y = 2] = []) {


return x + y; }
preFilledArray(); // 3
preFilledArray([]); // 3
preFilledArray([2]); // 4
preFilledArray([2, 3]); // 5
// Works the same for objects:
function preFilledObject({ z = 3 } = {}) {
return z;
}
preFilledObject(); // 3
preFilledObject({}); // 3
preFilledObject({ z: 2 }); // 2

Rest parameters:

The rest parameter syntax

- allows a function to accept an indefinite number of arguments as


an array, providing a way to represent variadic functions in
JavaScript.

function sum(...theArgs) {
let total = 0;
for (const arg of theArgs) {
total += arg;
}
return total;
}
console.log(sum(1, 2, 3));
// Expected output: 6

console.log(sum(1, 2, 3, 4));
// Expected output: 10

//using arguments object


function SumOfNum() {
let total = 0
for (let i = 0; i < arguments.length; i++) {
total += arguments[i]
}
return total;
}

console.log("Sum is ", SumOfNum(1, 2, 3, 4));


console.log("Sum is ", SumOfNum(1, 2, 5));

Basic Syntax:
function f(a, b, ...theArgs) {
// …
}

Example 2:
function myFun(a, b, ...manyMoreArgs) {
console.log("a", a);
console.log("b", b);
console.log("manyMoreArgs", manyMoreArgs);
}
myFun(1);

myFun("one", "two", "three", "four", "five", "six");

// Console Output:
// a, one
// b, two
// manyMoreArgs, ["three", "four", "five", "six"]
# A function definition can only have one rest parameter, and the rest
parameter must be the last parameter in the function definition.

Illegal function definition:

function wrong1(...one, ...wrong) {}


function wrong2(...wrong, arg2, arg3) {}

The rest parameter is not counted towards the


function's length property.

The major differences between rest parameters and


the arguments object:

 The arguments object is not a real array, while rest parameters


are Array instances, meaning methods
like sort(), map(), forEach() or pop() can be applied on it directly.

 The rest parameter bundles all the extra parameters into a single
array, but does not contain any named argument
defined before the ...restParam.
 The arguments object contains all of the parameters — including
the parameters in the ...restParam array — bundled into one array-
like object.

Example 3:
function multiply(multiplier, ...theArgs) {
return theArgs.map((element) => multiplier * element);
}

const arr = multiply(2, 15, 25, 42);


console.log(arr); // [30, 50, 84]
From arguments to an array

Array methods can be used on rest parameters, but not on the arguments object:

function sortRestArgs(...theArgs) {
const sortedArgs = theArgs.sort();
return sortedArgs;
}
console.log(sortRestArgs(5, 3, 7, 1)); // 1, 3, 5, 7

//using arguments object---Never works


function sortArguments() {
const sortedArgs = arguments.sort();
return sortedArgs; // this will never happen.
}

console.log(sortArguments(5, 3, 7, 1));
// throws a TypeError (arguments.sort is not a function)

The arguments object

arguments is an array-like object accessible inside functions that


contains the values of the arguments passed to that function.

function func1(a, b, c) {
console.log(arguments[0]);
// Expected output: 1

console.log(arguments[1]);
console.log(arguments[2]);
}
func1("Kumar","Karthik","ramesh");

The arguments object is a local variable available within all non-


arrow functions.
Rest Parameter is preferred compared to arguments object.

The arguments object is useful for functions called with more arguments
than they are formally declared to accept, called variadic functions, such
as Math.min().

This example function accepts any number of string arguments and


returns the longest one:

function longestString() {
let longest = "";
for (let i = 0; i < arguments.length; i++) {
if (arguments[i].length > longest.length) {
longest = arguments[i];
}
}
return longest;
}

Arguments.length -count how many arguments the function was called


with.
Function.length-count the no of arguments the function was declared to
accept.
Array's built-in methods like forEach() or map() are not accessible.

Template Literals
- embeding variables and expressions within your strings.
- template strings/template literals are denoted using backticks ``
-the syntax ${} which is used to embed variables within the string.
Multiline strings, readability and maintainability

const userName = 'Marie'


const balance = 10

// Using regular string


const str1 = 'Hi ' + userName + ',' + ' your balance is ' + balance + '.'
console.log("Regular string: ", str1)

// Using template literal


const str2 = `Hi ${userName}, your balance is ${balance}.`
console.log("Template literal: ", str2)

String interpolation:
const a = 5;
const b = 10;
//regular strings
console.log("Fifteen is " + (a + b) + " and\nnot " + (2 * a + b) + ".");

//template strings
console.log(`Fifteen is ${a + b} and
not ${2 * a + b}.`);
const user = {
name: "Marie",
age: 25,
};

const userProfile = `
<div>
<h2>Name: ${user.name}</h2>
<p>Age: ${user.age}</p>
</div>`

Dynamic sql queries


const tableName = "users";
const columnName = "name";
const searchValue = "John";

const sqlQuery =
`SELECT * FROM ${tableName} WHERE ${columnName} = '$
{searchValue}'

Tagged Templates: One of the features of Template Literals is its


ability to create Tagged Template Literals.
Tagged Literal is written like a function definition, but the difference is
when this literal is called,
There is no parenthesis() to a literal call. An array of Strings are passed
as a parameter to a literal.

<script>
function TaggedLiteralEg(strings) {
document.write(strings);
}
TaggedLiteralEg `Hai CSE students`;
</script>

Arrow Functions:
Arrow function {()=>} is concise way of writing JavaScript functions
in shorter way.
Lamda functions
Anonymous functions
Arrow functions can be defined with zero or more parameters, on one or
more lines.
Ex1:
() => expr

const cse = () => {


console.log( "Hi students" );
}
cse();
ex: 2
const materials = ['Hydrogen', 'Helium', 'Lithium', 'Beryllium'];
console.log(materials.map((material) => material.length));

In most cases, parentheses (( )) are not required to be around the


parameter list if there is one parameter.
They are required in the following cases:
 There is more than one parameter. (x,y)=> 2*x+3*y;
 There are no parameters. ( ) => { console.log(“one
parameter”, 24)
 The single parameter is a destructured object.
 There are default or rest parameters involved.
() => expression

param => expression

(param) => expression

(param1, paramN) => expression

() => {
statements
}

param => {
statements
}

(param1, paramN) => {


statements
}

Let result=(arg1,arg2,…,argn) => expression;

Is shorter version of

let func = function(arg1, arg2, ..., argN) {


return expression;
};

# Arrow function vs. regular function


 There are two main differences between an arrow function and a
regular function.
 First, in the arrow function,
the this, arguments, super, new.target are lexical. It means that the
arrow function uses these variables (or constructs) from the
enclosing lexical scope.
 Second, an arrow function cannot be used as a function
constructor. If you use the new keyword to create a new object
from an arrow function, you will get an error.

Rest parameters, default parameters, and destructuring within params


are supported, and always require parentheses:

(a, b, ...r) => expression


(a = 400, b = 20, c) => expression
([a, b] = [10, 20]) => expression
({ a, b } = { a: 10, b: 20 }) => expression

function ask(question, yes, no) {


if (confirm(question)) yes();
else no();
}
ask(
"Do you agree?",
() => alert("You agreed."),
() => alert("You canceled the execution.")
);

// Arrow function with rest parameter


var logStuff = (arg1, arg2, ...moreArgs) => {

console.log(arg1);
console.log(arg2);

// Logs an array of any other arguments you pass in after arg2


console.log(moreArgs);

};
logStuff('chicken', 'tuna', 'chips', 'cookie', 'soda', 'delicious');
// In this example...
// arg1 = 'chicken'
// arg2 = 'tuna'
// moreArgs = ['chips', 'cookie', 'soda', 'delicious']

Example2:
var add = (...args) => {
// Set a starting total
var total = 0;
// Add each number to the total
for (var i = 0; i < args.length; i++) {
total += args[i];
}
// Return to the total
return total;
};
add(2,3,4);

Arrow functions can be async by prefixing the expression with


the async keyword.

async param => expression


async (param1, param2, ...paramN) => {
statements
}

Decomposing a Traditional anonymous function into arrow function.


// Traditional anonymous function
(function (a) {
return a + 100;
});

// 1. Remove the word "function" and place arrow between the argument
and opening body brace
(a) => {
return a + 100;
};

// 2. Remove the body braces and word "return" — the return is implied.
(a) => a + 100;

// 3. Remove the parameter parentheses


a => a + 100;

The parentheses can only be omitted if the function has a single simple
parameter. If it has multiple parameters, no parameters, or default,
destructured, or rest parameters, the parentheses around the parameter
list are required.

// Traditional anonymous function


(function (a, b) {
return a + b + 100;
});

// Arrow function
(a, b) => a + b + 100;

const a = 4;
const b = 2;
// Traditional anonymous function (no parameters)
(function () {
return a + b + 100;
});

// Arrow function (no parameters)


() => a + b + 100;

Arrow functions cannot guess what or when you want to return.


// Traditional anonymous function
(function (a, b) {
const chuck = 42;
return a + b + chuck;
});

// Arrow function
(a, b) => {
const chuck = 42;
return a + b + chuck;
};
How to Use Array and object Destructuring in JavaScript

Arrow functions
let array = [10, 20, 30, 40, 50];
array.forEach(element => {
console.log(element);
});

let array = [10, 20, 30, 40, 50];


let squaredArray = array.map(element => element * element);

console.log(squaredArray);

let array = [10, 25, 30, 45, 50];


let evenNumbers = array.filter(element => element % 2 === 0);
console.log(evenNumbers);
let array = [1, 2, 3, 4, 5];

let sum = array.reduce((a, b) => a + b, 0);


console.log(sum);

Limitations :
Arrow functions functions are limited and can’t be used in all situations:
They do not have their own bindings to this or super, and should not be
used as methods.
They cannot be used as constructors.
They cannot use the special arguments keyword.

Higher-Order Arrow Functions in JavaScript


A Higher-Order function is a function that receives a function as
an argument otherwise returns the function as output. The higher-Order Arrow
function implies using arrow functions (in ES6) along with Higher-Order functions.

Example:
// Data set of students
var Students = [
{ rollNo: 21, name: 'Alpha', prizesWon: 1 },
{ rollNo: 22, name: 'Beta', prizesWon: 3 },
{ rollNo: 23, name: 'Gamma', prizesWon: 0 },
{ rollNo: 24, name: 'Delta', prizesWon: 0 },
{ rollNo: 25, name: 'Omega', prizesWon: 1}
];

// Use map() function with arrow functions


const StudentRollNo = Students.map(Student => Student.rollNo);

// Display Roll no data


console.log(StudentRollNo);

example 2:
// Using reduce() function with arrow functions
const totalPrizes = Students.reduce(
(accumulator, Student) => accumulator + Student.prizesWon, 0);

// Display total number of prizes won by all


console.log(totalPrizes);

// Sorting the 'workers' array based on the 'age' property in ascending


order
// Array of objects representing worker information
let workers = [
// Object for the first worker
{
firstName: 'Advin',
lastName: 'Mark',
age: 21,
joinDate: 'December 15, 2023'
},
// Object for the second worker
{
firstName: 'Marry',
lastName: 'Con',
age: 23,
joinDate: 'January 20, 2023'
},
// Object for the third worker
{
firstName: 'Harry',
lastName: 'Potter',
age: 32,
joinDate: 'February 25, 2023'
},
// Object for the fourth worker
{
firstName: 'Lily',
lastName: 'Potter',
age: 32,
joinDate: 'June 25, 2023'
}
];

Here an array named workers is declared. It has four objects. Each


object has four properties.

let sortedByAge = workers.sort((x, y) => x.age - y.age);


// Logging the sorted workers array to the console
console.log(sortedByAge);

let sortedByAge = workers.sort((x, y) => y.age - x.age); //descending


order

// Using the sort() method with a custom comparison function


// Sorting workers by their first names in ascending order

sortedByFirstName = workers.sort((x, y) => {


// Convert first names to lowercase for case-insensitive sorting
let fx = x.firstName.toLowerCase(), fy = y.firstName.toLowerCase();

// Return a negative value if x should be placed before y


if (fx < fy) {
return -1;
}

// Return a positive value if x should be placed after y


if (fx > fy) {
return 1;
}
// Return 0 if x and y have the same order
return 0;
});

// 'sortedByFirstName' now contains the worker's array sorted by first


names
console.log(sortedByFirstName);

// Sorting workers based on their joinDate property


// Using the sort() method with a custom comparison function
sortedByDate = workers.sort((x, y) => {
// Convert joinDate strings to Date objects
let dateX = new Date(x.joinDate);
let dateY = new Date(y.joinDate);

// Compare Date objects to achieve ascending sorting


return dateX - dateY;
});
console.log(sortedByDate);

sortedByName = workers.sort((x, y) => {


// Convert last names and first names to lowercase for case-
insensitive sorting
let lastNameX = x.lastName.toLowerCase();
let lastNameY = y.lastName.toLowerCase();
let firstNameX = x.firstName.toLowerCase();
let firstNameY = y.firstName.toLowerCase();

// Compare last names


if (lastNameX < lastNameY) {
return -1;
}
if (lastNameX > lastNameY) {
return 1;
}

// If last names are equal, compare first names


if (firstNameX < firstNameY) {
return -1;
}
if (firstNameX > firstNameY) {
return 1;
}

// If both last names and first names are equal, return 0


return 0;
});
console.log(sortedByName);

Destructuring:
Destructuring is a JavaScript expression that makes it possible to
unpack values from arrays, or properties from objects, into distinct
variables.
javaScript object destructing is about assigning properties to an object to
the individual variables.
It is about extracting data from arrays and objects and assign them to
variables.
Benefits while extracting the data from Arrays or object properties from
objects repeatedly.
let introduction = ["Hello", "I" , "am", "Sarah"];
let [greeting, pronoun] = introduction;

console.log(greeting);
console.log(pronoun);
or
let greeting, pronoun;
[greeting, pronoun] = ["Hello", "I" , "am", "Sarah"];

console.log(greeting);
console.log(pronoun);

let a, b, rest;
[a, b] = [10, 20];

console.log(a);
// Expected output: 10

console.log(b);
// Expected output: 20
[a, b, ...rest] = [10, 20, 30, 40, 50];

console.log(rest);
// Expected output: Array [30, 40, 50]

let [greeting,,,name] = ["Hello", "I" , "am", "Sarah"];


console.log(greeting);
console.log(name);

*The comma separator is used to skip values in an array.

let [,pronoun,,name] = ["Hello", "I" , "am", "Sarah"];

console.log(pronoun);
console.log(name);

Assigning the rest of an array


What if we want to assign some of the array to variables and the rest of
the items in an array to a particular variable? In that case, we would do
this:

let [greeting,...intro] = ["Hello", "I" , "am", "Sarah"];

console.log(greeting);
console.log(intro);

Destructuring Assignment with Functions


function getArray() {
return ["Hello", "I" , "am", "Sarah"];
}
let [greeting,pronoun] = getArray();

console.log(greeting);
console.log(pronoun);

Using Default Values


Default values can be assigned to the variables just in case the value
extracted from the array is undefined:

let [greeting = "hi",name = "Sarah"] = ["hello"];


console.log(greeting);//"Hello"
console.log(name);//"Sarah"

Basic Object Destructuring


let person = {name: "Sarah", country: "Nigeria", job: "Developer"};

let {name, country, job} = person;

console.log(name);//"Sarah"
console.log(country);//"Nigeria"
console.log(job);//Developer"

let {name, country, job} = {name: "Sarah", country: "Nigeria", job:


"Developer"};

console.log(name);//"Sarah"
console.log(country);//"Nigeria"
console.log(job);//Developer"

Variables declared before being assigned


The ( ) around the assignment statement is required syntax when using
the object literal destructuring assignment without a declaration.
{} on the left hand side is considered a block and not an object literal.
let person = {name: "Sarah", country: "Nigeria", job: "Developer"};
let name, country, job;

({name, country, job} = person);

console.log(name);//"Sarah"
console.log(job);//"Developer"

Using a new Variable Name


let person = {name: "Sarah", country: "Nigeria", job: "Developer"};

let {name: identity, job: res} = person;

console.log(identity);//"Sarah"
console.log(res);//"Developer"

Computed Property Name


Computed property name is another object literal feature that works for
destructuring, where you can specify the name of a property via an
expression if you put it in square brackets:

let prop = "name";

let {[prop] : foo} = {name: "Sarah", country: "Nigeria", job:


"Developer"};

console.log(foo);//"Sarah"
Promises in Java Script

A callback in JavaScript is simply a function that is passed as an


argument to another function and is executed at a later time or
when a specific condition is met.
Callbacks are essential for handling asynchronous operations, such
as fetching data from a server, reading a file, or waiting for a timer
to expire.

<!DOCTYPE html>
<html>
<body>
<script>
let success = true;
function getUsers() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (success) {
resolve([
{ username: 'john', email: 'john@test.com' },
{ username: 'jane', email: 'jane@test.com' },
]);
} else {
reject('Failed to find the user list');
}
}, 1000);
});
}

const promise = getUsers();


const consumer=(usern)=>{
promise.then(
(users)=> {
const user=users.find((user)=>user.username===usern)
console.log(`Email of ${usern}=${user.email}`)
console.log("list of users",users);
},
(error)=> console.log("couldnt find",error) );
}
consumer('jane');
</script>
</body>
</html>
Promises are JavaScript objects which represent the completion or
failure of asynchronous operations.

It produces a value after an asynchronous (aka, async) operation


completes successfully, or an error if it does not complete successfully
due to time out, network error, and so on.

Successful call completions are indicated by the resolve function call, and
errors are indicated by the reject function call.

promise constructor

let promise = new Promise(function(resolve, reject) {


// Code to execute
});
The constructor function takes a function as an argument. This function
is called the executor function.

A promise object has the following internal properties:


1. state – This property can have the following values:
 pending: Initially when the executor function starts the execution.
 fulfilled: When the promise is resolved.
 rejected: When the promise is rejected.

2. result – This property can have the following values:


 undefined: Initially when the state value is pending.
 value: When resolve(value) is called.
 error: When reject(error) is called.
promise.then(
(result) => {
console.log(result);
},
(error) => {
console.log(error);
}
);

function getUsers() {
return [
{ username: 'john', email: 'john@test.com' },
{ username: 'jane', email: 'jane@test.com' },
];
}
function findUser(username) {
const users = getUsers();
const user = users.find((user) => user.username === username);
return user;
}
console.log(findUser('john'));

sync operation & block code.

function getUsers(callback) {
setTimeout(() => {
callback([
{ username: 'john', email: 'john@test.com' },
{ username: 'jane', email: 'jane@test.com' },
]);
}, 1000);
}

function findUser(username, callback) {


getUsers((users) => {
const user = users.find((user) => user.username === username);
callback(user);
});
}

findUser('john', console.log);

Example

let success = true;

function getUsers() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (success) {
resolve([
{ username: 'john', email: 'john@test.com' },
{ username: 'jane', email: 'jane@test.com' },
]);
} else {
reject('Failed to the user list');
}
}, 1000);
});
}

function onFulfilled(users) {
console.log(users);
}
function onRejected(error) {
console.log(error);
}

const promise = getUsers();


promise.then(onFulfilled, onRejected);

// getUsers() function
// ...

const promise = getUsers();


promise.then(
(users) => console.log,
(error) => console.log
);

Advantages of Using Promises in JavaScript


1. Improved Readability: Promises provide a more readable and
sequential way to handle asynchronous operations compared to callback
functions.
2. Error Handling: Promises have built-in error handling through
the .catch() method, making it easier to manage errors in asynchronous
code.

Function Generator or Generator:


In JavaScript, a regular function is executed based on run-to-completion
model.
It cannot pause midway and then continues from where it paused. For
example:
function regFun() {
console.log('I');
console.log('cannot');
console.log('pause');
}

Execution : top to bottom approach


Only way to exit regFun() : is by returning from it or throwing an error.
If you invoke the foo() function again, it will start the execution from the
top to bottom.

ES6 introduces a new kind of function that is different from a regular


function: function generator or generator.
A generator function is a special type of function in JavaScript that
allows pausing and resuming its execution during runtime.
A generator can pause midway and then continues from where it paused.
For example:
function* generate() {
console.log('invoked 1st time');
yield 1;
console.log('invoked 2nd time');
yield 2;
}

 First, you see the asterisk (*) after the function keyword. The
asterisk denotes that the generate() is a generator, not a normal
function.
 Second, the yield statement returns a value and pauses the
execution of the function.

The following code invokes the generate() generator:


 let gen = generate();

invoking the generate() generator:

 First, you see nothing in the console. If the generate() were a


regular function, you would expect to see some messages.
 Second, you get something back from generate() as a returned
value.

let gen=generator();

console.log(gen);
Object [Generator] {}

It returns a Generator object without executing its body when it is


invoked.
It has next () and return () .
The Generator object returns another object with two
properties: done and value. In other words, a Generator object
is iterable.
let result = gen.next();
console.log(result);

invoked 1st time


{value: 1, done: false }

the Generator object executes its body which outputs message 'invoked
1st time' at line 1 and returns the value 1 at line 2.
The yield statement returns 1 and pauses the generator at line 2.

next() on generator() gives

invoked 2nd time


{ value: 2, done: false }

Third time
result= gen.next();
console.log(result);
{ value: undefined, done: true }

General syntax:
function* name(param0) {
statements
}
function* name(param0, param1) {
statements
}
function* name(param0, param1, /* …, */ paramN) {
statements
}
Since a generator is iterable, you can use the for...of loop:
for (const g of gen) {
console.log(g);
}

Generator to generate a never-ending sequence:

function* forever() {
let index = 0;
while (true) {
yield index++;
}
}

let f = forever();
console.log(f.next());
console.log(f.next());
console.log(f.next());

Using a generator function to implement Bag Data Structure:

A Bag is a data structure that has the ability to collect elements and
iterate through elements. It doesn’t support removing items.

class Bag {
constructor() {
this.elements = [];
}
isEmpty() {
return this.elements.length === 0;
}
add(element) {
this.elements.push(element);
}
* [Symbol.iterator]() {
for (let element of this.elements) {
yield element;
}
}
}

let bag = new Bag();

bag.add(1);
bag.add(2);
bag.add(3);

for (let e of bag) {


console.log(e);
}

Summary

 Generators are created by the generator function function* f(){}.


 Generators do not execute its body immediately when they are
invoked.
 Generators can pause midway and resumes their executions where
they were paused. The yield statement pauses the execution of a
generator and returns a value.
 Generators are iterable so you can use them with the for...of loop.

Limitation of Generators: You can’t yield inside a callback in a


generator.
Generator functions cannot be represented by using the arrow
functions.

function* generator() {
['a', 'b', 'c'].forEach(value => yield value)
// This will give syntax error
}
Benefits of Generator Functions

 Simpler Asynchronous Code: Generator functions provide a


cleaner and more sequential way to handle asynchronous
operations compared to a traditional callback or promise-based
approaches.
 Lazy Evaluation: Generator functions allow for lazy evaluation
of data streams. They produce values on demand, reducing
memory usage and improving performance when dealing with
large datasets.
 Custom Iterators: Generator functions simplify the creation of
custom iterators, making it easier to iterate over custom data
structures or implement unique traversal patterns.
 Stateful Execution: Generator functions retain their state
between invocations, allowing for resumable computation and
maintaining context across multiple function calls.
Simplified Control Flow: The ability to pause and resume
execution simplifies complex control flow scenarios.

Benefits of Integrating Generators into Promises


1. Streamlined Data Fetching: Combining Generators and Promises can
lead to more efficient data fetching strategies, especially when dealing
with paginated API calls.
2. Complex Workflow Handling: For scenarios where complex workflows
involve multiple asynchronous steps, the combination of Generators and
Promises can enhance code clarity.
3. unified and clear approach for handling errors in the asynchronous
code as promises provide .catch () for dealing with errors.
Referenced Url: https://www.dhiwise.com/post/the-pros-and-cons-of-
integrating-javascript-generators-into-promises
Code Examples Illustrating Integration Techniques:
<!DOCTYPE html>
<html>
<body>
<script>
function* fetchDataGenerator(a) {
let d2;
const data1 = yield fetchData1(a,d2);
const data2 = yield fetchData2(data1);
return data2;
}
function fetchData1(x=2,y=3) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(x*y);
}, 1000);
});
}
function fetchData2(previousdata) {
return new Promise((resolve) => {
let z=0;
setTimeout(() => {
resolve(z=previousdata * 10);
}, 1000);
});
}

const generator = fetchDataGenerator(10);


const { value, done } = generator.next();

if (!done) {
value.then((result) => {
//console.log("Initial fetch result=",result)
const nextStep = generator.next(result);
if (!nextStep.done) {
nextStep.value.then((finalResult) => {
console.log("Final result=",finalResult);
});
}
});
}
</script>
</body>
</html>
‘Async/ await’ in asynchronous programming

async makes a function return a Promise

await makes a function wait for a Promise

The keyword async before a function makes the function return a


promise:
Async await simplify writing promise -based code as if it was
synchronous and ensures that there is no braking of execution in
main thread.
syntax:
async function name(param0) {
statements
}
async function name(param0, param1) {
statements
}
async function name(param0, param1, /* …, */ paramN) {
statements
}
Example:
async function myFunction() { return "Hi"; }
/*let promise1=myFunction()
await promise1; */
same as
function myFunction(){ return Promise.resolve(“Hi”);}

async functions always return a promise. If the return value of an async


function is not explicitly a promise, it will be implicitly wrapped in a
promise.

consuming/using promise
myFunction().then(
function(value) { /* code if successful */ },
function(error) { /* code if some error */ }
);

The await keyword can only be used inside an async function, we get
syntax error
The await keyword makes the function pause the execution and wait for
a resolved promise before it continues:

Syntax :
let value = await promise;

//example program : return a list of User objects


<!DOCTYPE html>
<html>
<body>
<h1>JavaScript async / await</h1>
<h2 id="demo"></h2>
<script>
async function myDisplay() {
let myPromise = new Promise(function(resolve, reject) {
resolve([
{ username: 'john',userid:101,basic:10000,da:8000,hra:1000},
{ username: 'jane',userid:201,basic:20000,da:17000,hra:2000},
{ username:'ramesh',userid:301,basic:30000,da:26000,hra:3000}
]);
});
document.getElementById("demo").innerHTML = await myPromise;
}

myDisplay();
</script>
</body>
</html>

Spread Operator
The spread operator (...) in JavaScript is used to expand elements of an
iterable (like an array or string) or object into places where multiple
elements/properties are expected.

 The spread operator is denoted by three dots (...).


 unpacks elements of iterable objects such as arrays, sets, and maps
into another iterable or function call.

//copying array
const array1 = [1, 2, 3];
const array2 = [...array1]; // Creates a copy of array1
console.log(array2); // Output: [1, 2, 3]

ex:2
const odd = [1,3,5];
const combined = [2,4,6, ...odd];
console.log(combined);

function f(a, b, ...args) {


console.log(args);
}
f(1, 2, 3, 4, 5);

diff between spread and rest operator.

 The spread operator (...) unpacks the elements of an iterable


object.
 The rest parameter (...) packs the elements into an array.

The rest parameters must be the last arguments of a function. However,


the spread operator can be anywhere:
// Pass array elements as arguments to a function using the spread
operator
const numbers = [1, 2, 3];

const sum = (a, b, c) => a + b + c;

console.log(sum(...numbers)); // Output: 6

Concatenating Arrays:
const array1 = [1, 2, 3];
const array2 = [4, 5, 6];
const concatenatedArray = [...array1, ...array2]; // Combines both arrays

console.log(concatenatedArray); // Output: [1, 2, 3, 4, 5, 6]

const odd = [1,3,5];


const combined = [2,...odd, 4,6];
console.log(combined);

let chars = ['A', ...'BC', 'D'];


console.log(chars); // ["A", "B", "C", "D"]

Passing Array Elements as Function Arguments:


const numbers = [1, 2, 3, 4, 5];
const maxNumber = Math.max(...numbers); // Spread the array to get
individual elements
console.log(maxNumber); // Output: 5

Creating copies of objects


const obj1 = { a: 1, b: 2 };
const obj2 = { ...obj1 }; // Creates a copy of obj1
console.log(obj2); // Output: { a: 1, b: 2 }

Merging Objects:
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const mergedObject = { ...obj1, ...obj2 }; // Merges both objects
console.log(mergedObject); // Output: { a: 1, b: 2, c: 3, d: 4 }

Cloning Objects with Nested Structure/ Copying a nested array


The spread operator will deep copy the top-level elements of array1 but
shallow copy the nested arrays.

let array1 = ['h', ['e', [1, true, 9], "hello"], ['y']];


If we make changes at array1[0], the data at array2[0] will not be
affected, but if we make changes at array1[1], the data at array2[1] will
also change.

// Copying array1 to array2 using spread operator


let array2 = [...array1];

// Printing array2 after copying


console.log("Array 2 after nested copying:\n", array2);

// Updating array1[0] from 'h' to 'a'


array1[0] = 'a'

// Printing array2 after updating array1[0]


console.log("Array 2 after updating array1[0] to 'a':\n", array2);

// Updating array1[1][0] from 'e' to 'b'


array1[1][0] = 'b'
// Printing array1 after updating array1[1][0]
console.log("Array 1 after updating array1[1][0] to 'b':\n", array1);

// Printing array2 after updating array1[1][0]


console.log("Array 2 after updating array1[1][0] to 'b':\n", array2);

example 2:

const obj1 = { a: { b: 1, c: 2 } };
const obj2 = { ...obj1 };
console.log(obj2); // Output: { a: { b: 1, c: 2 } }

// To create a deep clone


const deepClone = JSON.parse(JSON.stringify(obj1));
console.log(deepClone); // Output: { a: { b: 1, c: 2 } }

The spread operator is a powerful feature in JavaScript that allows for


concise and expressive code when dealing with arrays and objects.
ES2018 expands the spread operator to objects, which is known as object
spread.

let result = compare(...[1, 2]); //


console.log(result); // -1

Better way to use the Array’s push ()


//using apply
let rivers = ['Nile', 'Ganges', 'Yangte'];
let moreRivers = ['Danube', 'Amazon'];

[].push.apply(rivers, moreRivers);
console.log(rivers);

Modules in JavaScript
A Module is a chunk of code in an external file that performs a specific
task or function.
Modules allow you to split your code into smaller, self-contained units
that can be reused and maintained more easily.
Modules promote modularity , code readability and reduce the likelihood
of errors, enabling JavaScript developers to focus on specific
functionalities without getting overwhelmed by the entire program's
complexity.

The benefits of using modules are:

 Reusability: Modules are versatile components that can be


reused across multiple programs or applications.
 Hiding Information: Modules can be used to hide
information from other parts of the program, which can improve
security and prevent unexpected side effects.
 Code organization: Modules can help you to organize your
code in a more logical and maintainable way.
 Encapsulation: Modules can be used to encapsulate
functionality, which helps to hide implementation details and
make code more robust.
 Collaboration: Modules can make it easier for multiple
developers to collaborate on a project since each developer can
work on their own module without having to worry about the
other modules.

In JavaScript, we use the import and export keywords to share and


receive functionalities respectively across different modules.
 The export keyword is used to make a variable, function, class, or
object accessible to other modules. In other words, it becomes a
public code.
 The import keyword is used to bring in public code from another
module.

If you want to import every public function from another module, use
the asterisk * keyword:

import * as newlyExport from './otherModule'

different variants of imports declaration: // these variants depend on


export declaration
import defaultExport from "module-name";
import * as name from "module-name";
import { export1 } from "module-name";
import { export1 as alias1 } from "module-name";
import { default as alias } from "module-name";
import { export1, export2 } from "module-name";
import { export1, export2 as alias2, /* … */ } from "module-name";
import { "string name" as alias } from "module-name";
import defaultExport, { export1, /* … */ } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name";
Iterators :
Control over data iteration- iterators

In JavaScript, an iterator function is a unique function that returns


an iterator object.

An iterator object is an object that has a next() method, which returns


an object with two properties: value and done.

The value property represents the next value in the sequence, and
the done property indicates whether the iterator has reached the end
of the sequence.

provide a mechanism for customizing the looping through data


structures such as customizing the behavior of for…of loops.

Iterator functions can be used to iterate over collections of data,


such as arrays or objects.

function Iterator(array) {
let nextIndex = 0;
return {
next: function () {
if (nextIndex < array.length) {
return {
value: array[nextIndex++],
done: false,
};
} else {
return {
value: undefined,
done: true,
};
}
},
};
}
const array = [1, 2, 3, 4, 5];
const arrayValue = Iterator(array);

console.log(arrayValue.next()); // { value: 1, done: false }


console.log(arrayValue.next());
console.log(arrayValue.next());
console.log(arrayValue.next());
console.log(arrayValue.next());
console.log(arrayValue.next());

/*const array = [1, 2, 3, 4, 5];


const iterator = array[Symbol.iterator]();

console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

*/

Symbol
new type of primitive data type introduced in ES6
Symbol() - Symbols return unique identifiers
Used for representing unique values that can be used as identifiers or
keys of objects that won’t collide with keys of any other code that might
add to the object.

They are used as object properties that cannot be recreated.


It basically helps us to enable encapsulation or information hiding
Symbols can be used to create private properties and methods in classes.
Symbols are useful for creating unique constants especially useful while
creating APIs , can be shared across different parts of your code.

Symbol() function will create a Symbol whose value remains unique


throughout the lifetime of the program.

const sym1=Symbol();

typeof sym1;// symbol

console.log(sym1) ; // Symbol()

let id =Symbol(“id desc”);

Symbols are guaranteed to be unique. Even if we create many symbols


with exactly the same description, they are different values.

<!DOCTYPE html>
<script>
"use strict";

let id1 = Symbol("id");


let id2 = Symbol("id");

alert(id1 == id2); // false


</script>

Symbols don’t auto-convert to a string

let id = Symbol("id");
alert(id); // TypeError: Cannot convert a Symbol value to a string

let id = Symbol("id");
alert(id.toString()); // Symbol(id), now it works

let id = Symbol("id");
alert(id.description); // id

Hidden” properties
Symbols allow us to create “hidden” properties of an object, that no other
part of code can accidentally access or overwrite.
For instance, if we’re working with user objects, that belong to a third-
party code.
<!DOCTYPE html>
<script>
"use strict";

let user = { // belongs to another code


name: "John"
};

let id = Symbol("id");

user[id] = 1;

alert( user[id] ); // we can access the data using the symbol as the key
</script>

Symbols can also be used to create constants, which are useful for
creating APIs. Constants are created by passing in an object with a
'constant' property set to true.

// Create a constant
const MY_CONSTANT = Symbol('MY_CONSTANT', { constant: true });

object keys:
const mySymbol = Symbol();
const myObject = {
[mySymbol]: 'Hello World'
};
console.log(myObject[mySymbol]);
// Create a constant
const MY_CONSTANT = Symbol('MY_CONSTANT', { constant: true });

// Create another constant


const MY_OTHER_CONSTANT = Symbol('MY_CONSTANT',
{ constant: true });

console.log(MY_CONSTANT === MY_OTHER_CONSTANT);


// expected output: false

Global symbols
If we want named symbols to have same entities. For instance, different
parts of our application want to access symbol "id" meaning exactly the
same property.
To achieve that, there exists a global symbol registry.

// Create a global Symbol


const myGlobalSymbol = Symbol('myGlobalSymbol', { global: true });
want to read global symbol with the method Symbol.for(key)

<!DOCTYPE html>
<script>
"use strict";

// read from the global registry


let id = Symbol.for("id"); // if the symbol did not exist, it is created
// read it again (maybe from another part of the code)
let idAgain = Symbol.for("id");

// the same symbol


alert( id === idAgain ); // true
</script>

// get symbol by name


let sym = Symbol.for("name");
let sym2 = Symbol.for("id");

// get name by symbol


alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id

Static methods

Methods like Symbol.for() and Symbol.keyFor() are used for setting and
retrieve Symbols from the global Symbol registry.

Note that the "global Symbol registry" is only a fictitious concept and
may not correspond to any internal data structure in the JavaScript
engine.
The method Symbol.for(Stringkey) takes a string key and returns a
symbol value from the registry, Otherwise a new Symbol gets created in
the global Symbol registry with key.
while Symbol.keyFor(symbolValue) takes a symbol value and returns the
string key corresponding to it.

Well-known Symbols

All static properties of the Symbol constructor are Symbols themselves,


whose values are constant across realms.

-known as well-known Symbols,


-serve as "protocols" for certain built-in JavaScript operations.

Examples:

Symbol.iterator

A method returning the default iterator for an object. Used


by for...of.

Symbol.asyncIterator

A method that returns the default AsyncIterator for an object. Used


by for await...of.

Symbol.replace

A method that replaces matched substrings of a string. Used


by String.prototype.replace().

Instance properties

These properties are defined on Symbol.prototype and shared by


all Symbol instances.

Symbol.prototype.constructor

The constructor function that created the instance object.


For Symbol instances, the initial value is the Symbol constructor.

Symbol.prototype.description

A read-only string containing the description of the Symbol.

Symbol type conversions

things to note when working with type conversion of Symbols.

 When trying to convert a Symbol to a number, a TypeError will be


thrown (e.g. +sym or sym | 0).
 Symbol("foo") + "bar" throws a TypeError (can't convert Symbol to
string). This prevents you from silently creating a new string property
name from a Symbol.

let lib = {
name: "ABC",
};

lib["id"] = 5;
lib["id"] = 6; // The value is changed because it is String [KEY]!!

lib[Symbol("id")] = 123;
lib[Symbol("id")] = 124; //Not changed

console.log(lib); // { name: "ABC", id: 6, Symbol(id): 123, Symbol(id):


124 }

You might also like