Short Q A - rs-hash/Senior GitHub Wiki
React
Javascript
1.common function to implement both sum(1,2) and sum(1)(2)
function sum(a, b) {
if (b === undefined) {
return function (b) {
return a + b;
}
return a + b;
}
}
2. Currying - getSum(5)(4)(3)(2)(1)
const getSum = (a) => (b) => (c) => (d) => (e) => a + b + c + d + e
3. Find output
typeof(5+"5"-5) = number
5 + "5" = "55"
"55" - 5 = 50
typeof(50) = number
4. setTimeout & setInterval
function sayHi(phrase, who) {
alert( phrase + ', ' + who );
}
setTimeout(sayHi, 1000, "Hello", "John"); // Hello, John
let timerId = setTimeout(() => alert("never happens"), 1000);
alert(timerId); // timer identifier
clearTimeout(timerId);
alert(timerId); // timer identifier will print
// repeat with the interval of 2 seconds
let timerId = setInterval(() => alert('tick'), 2000);
// after 5 seconds stop
setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);
Zero delay setTimeout
setTimeout(() => alert("World"));
alert("Hello"); // Hello World
5. HTTP Methods
GET - is used to request data from a specified resource.
can be cached and bookmarked, should not be used for sensitive data or data that'll cause side effect
fetch('api')
.then(response => response.json())
.then(data => console.log(data))
.catch(err => console.error("error" + err))
POST - is used to send data to a server to create/update a resource.
sends data in the request body, used for sensitive data that cause side effects, on click of back button data will be re-submitted. POST is a little safer than GET because the parameters are not stored in browser history or in web server logs
const postData = {
username: 'example_name',
password: 'example_password'
};
const postDataAsync = async () => {
try {
const response = await fetch('api', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(postData)
})
if (!response.ok) throw new Error('Failed to Fetch')
const data = await response.json()
console.log(data)
} catch (error) {
console.error('Error posting data:', error);
}
}
PUT - is used to send data to a server to create/update a resource.
- The difference between POST and PUT is that PUT requests are idempotent. That is, calling the same PUT request multiple times will always produce the same result. In contrast, calling a POST request repeatedly have side effects of creating the same resource multiple times.
- Generally, when a PUT request creates a resource the server will respond with a 201 (Created), and if the request modifies existing resource the server will return a 200 (OK) or 204 (No Content).
DELETE - deletes the specified resourced and Idempotent
const deleteUser = async () => {
try {
const response = await fetch('api/user/123', {
method: 'DELETE'
});
if (response.ok) {
console.log('deleted successfully')
} else {
console.error('Error deleting user')
}
} catch (error) {}
}
PATCH - similar to POST and PUT & only apply partial modifications to the resource
- A PATCH request is one of the lesser-known HTTP methods, but it is similar to POST and PUT.
- The difference with PATCH is that you only apply partial modifications to the resource.
- The difference between PATCH and PUT, is that a PATCH request is non-idempotent (like a POST request).
- To expand on partial modification, say you’re API has a /users/{{userid}} endpoint, and a user has a username.
- With a PATCH request, you may only need to send the updated username in the request body - as opposed to POST and PUT which require the full user entity.
const someData = {
completed: true // update that task is completed
}
const patchMethod = {
method: 'PATCH', // Method itself
headers: {
'Content-type': 'application/json; charset=UTF-8' // Indicates the content
},
body: JSON.stringify(someData) // We send data in JSON format
}
// make the HTTP patch request using fetch api
fetch(url, patchMethod)
.then(response => response.json())
.then(data => console.log(data)) // Manipulate the data retrieved back, if we want to do something with it
.catch(err => console.log(err)) // Do something with the error
HEAD - method is almost identical to GET, except without the response body.
- In other words, if GET /users returns a list of users, then HEAD /users will make the same request but won't get back the list of users.
- HEAD requests are useful for checking what a GET request will return before actually making a GET request.
- For example, if a URL might produce a large download, a HEAD request could read its Content-Length header to check the filesize without actually downloading the file.
HTTP/1.1 200 OK
Date: Mon, 23 Sept 2021 12:28:53 GMT
Server: Apache/2.2.14 (Win32)
Last-Modified: Wed, 22 Jul 2021 19:15:56 GMT
ETag: "34aa387-d-1568eb00"
Vary: Authorization,Accept
Accept-Ranges: bytes
Content-Length: 88
Content-Type: application/json
Connection: Closed
OPTIONS - requests permitted communication options for a given URL or server.
- A client can specify a URL with this method, or an asterisk (*) to refer to the entire server. The OPTIONS request should return data describing what other methods and operations the server supports at the given URL
- In CORS, a preflight request is sent with the OPTIONS method so that the server can respond if it is acceptable to send the request.
OPTIONS * HTTP/1.1
Host: medium.com
Origin: https://medium.com/
And the server response:
HTTP/1.1 200 OK
Allow: GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS
Access-Control-Allow-Origin: https://medium.com/
Access-Control-Allow-Methods: GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS
Access-Control-Allow-Headers: Content-Type
TRACE The HTTP TRACE method is designed for diagnostic purposes.
It performs a message loop-back test along the path to the target resource, providing a useful debugging mechanism.
6. HTTP Status codes
- 1xx informational
- 2xx success
- 3xx Redirection
- 4xx Client Error
- 5xx Server Error
- 100 ( Continue )
- 101 ( Switching Protocols )
- 200 (OK): Request Successful
- 201 (Created): New resource created.
- 204 (No Content)
- 300 (Multiple Choices)
- 301 (Moved Permanently)
- 302 (Found) Moved Temporarily
- 304 (Not Modified)
- 400 (Bad Request)
- 401 (Unauthorized): ( requires user authentication ) I need you to identify yourself before I can finish this request. [Note: This is one of the more unfortunately named headers. It should really be titled Unauthenticated; 403 is more like Unauthorized.]
- 403 (Forbidden): You asked for something you're not allowed to have.
- 404 (Not Found): You asked for a resource, but there isn't one that matches your description.
- 500 (Server Error): Something went wrong, so I can't give you what you asked for right now. Sorry about that.
- 502 (Bad Gateway): I don't support that kind of request right now.
- 503 (Service Unavailable): I'm not able to respond to requests right now.
- 504 Gateway Timeout
7. var, let, const
var
is function-scoped and hoisted,let
andconst
are block scoped and not hoisted asvar
const
variables cannot be reassigned after initialization- function declarations with
let
,const
cannot be called before their declaration in code
- var:
- function scoped ( visible throughout the entire function in which they are declared, regardless of block scope )
- can be redeclared and reassigned
- hoisted to the top of the containing function - available for use before declaration
- can be called before actual declaration in the code
- let:
- block scoped - only accessible within the block in which they are declared
- can be reassigned cannot be redeclared within the same scope
- they are hoisted to the top of their block scope but they are not initialized until the actual declaration is encountered during runtime known as temporal dead zone
- const:
- block scoped similar to let
- cannot be reassigned or redeclared within the same scope
- they must be initialized during declaration
- if the value of const is Object or Array, elements can be modified but the variable cannot be reassigned
8. Hoisting
Hoisting is a JavaScript mechanism where variables and function declarations are moved to the top of their containing scope during the compilation phase, before the code is executed. This means that regardless of where variables and function declarations are declared within their scope, they are treated as if they were declared at the top of the scope.
There are two main types of hoisting:
Variable Hoisting:
When variables declared with var are hoisted, only the variable declaration (not the initialization) is hoisted to the top of the scope. This means that the variable is accessible (but undefined) from the start of the scope until the actual declaration is reached during runtime. Example:
console.log(x); // Output: undefined
var x = 10;
Function Hoisting:
Function declarations are hoisted in their entirety, including both the declaration and the function body. This means that functions declared with function keyword can be called before their actual declaration in the code.
However, it's important to note that hoisting applies only to function declarations and variable declarations using var, not let or const. Variables declared with let or const are hoisted to the top of their block scope, but they are not initialized until the actual declaration is encountered during runtime. This is known as the "temporal dead zone" and can lead to ReferenceErrors if accessed before initialization.
9. How Javascript works in browser
1. JavaScript Runtime:
- JavaScript is single-threaded, meaning it can only execute one piece of code at a time.
- The JavaScript runtime environment consists of several components, including the call stack, heap, event loop, microtask queue, and callback queue.
2. Call Stack:
- The call stack is a data structure that keeps track of function calls and their execution context.
- When a function is called, its execution context is pushed onto the call stack. When the function completes, its execution context is popped off the stack.
3. Heap:
The heap is a region of memory where objects are stored. It is used to allocate memory for variables, objects, and other data structures.
4. Event Loop:
- The event loop is responsible for handling asynchronous operations and scheduling tasks for execution.
- The event loop is a mechanism that continuously checks the call stack and various task queues to determine which tasks need to be executed.
5. Microtask Queue:
- The microtask queue holds tasks that need to be executed as soon as possible, typically after the current synchronous code but before the next frame of rendering.
- Microtasks include promise handlers (.then, .catch, .finally) and other similar asynchronous tasks.
- Microtasks are prioritized over tasks in the callback queue and are executed before the event loop moves on to process tasks in the callback queue.
6. Callback Queue (Task Queue):
- The callback queue (also known as the task queue or macrotask queue) holds tasks that need to be executed asynchronously after the current synchronous code has finished executing.
- Tasks in the callback queue include events like DOM manipulation, HTTP requests, I/O operations, and timeouts scheduled using setTimeout and setInterval.
- When the call stack is empty, the event loop checks the callback queue for pending tasks. If there are tasks in the callback queue, the event loop moves them to the call stack for execution.
7. Concurrency Model:
- JavaScript's concurrency model is based on the event loop.
- When the call stack is empty, the event loop first checks the microtask queue.
- If the microtask queue is not empty, the event loop moves microtasks to the call stack for execution.
- After executing all microtasks, the event loop checks the callback queue and moves tasks to the call stack for execution.
8.Non-Blocking I/O:
JavaScript is non-blocking, meaning that it can perform other tasks while waiting for asynchronous operations to complete. This allows JavaScript to handle I/O operations efficiently without blocking the main thread.
9.Example:
Consider an example where you have synchronous code, asynchronous API calls, and promise handlers:
console.log('Start');
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
console.log('End');
In this example, 'Start' and 'End' are logged immediately because fetch is asynchronous. When the HTTP request completes, its promise settles, and its .then handler is added to the microtask queue. After the synchronous code completes, the event loop moves the promise's .then handler from the microtask queue to the call stack for execution.
10. Synchronous vs Asynchronous
Synchronous Tasks:
- Synchronous tasks are executed sequentially, one after another, in the order they appear in the code.
- When a synchronous task is executed, it blocks the execution of subsequent code until it completes.
Example:
console.log('First');
console.log('Second');
console.log('Third');
In this example, 'First', 'Second', and 'Third' will be logged to the console in sequential order.
Asynchronous Tasks:
- Asynchronous tasks are executed independently of the main program flow and don't block subsequent code execution.
- Asynchronous tasks are typically used for operations that take some time to complete, such as fetching data from an external server, reading files from disk, or waiting for user input.
- Asynchronous tasks allow the program to continue executing other code while waiting for the asynchronous operation to complete.
- Examples of asynchronous tasks include using promises, callbacks, async/await, setTimeout, setInterval, and DOM events like click, fetch, and XMLHttpRequest.
Example:
console.log('First');
setTimeout(() => {
console.log('Second');
}, 1000);
console.log('Third');
In this example, 'First' and 'Third' will be logged immediately, while 'Second' will be logged after a delay of 1000 milliseconds.
11. Make Asynchronous execute in synchronous
To make an asynchronous task behave synchronously in JavaScript, you can use async/await syntax. This allows you to write asynchronous code in a more synchronous-looking manner.
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error:', error);
}
}
fetchData();
12. Closure
- A closure in JavaScript is a feature that allows a function to access and remember its lexical scope, even when that function is executed outside of that lexical scope.
- In simpler terms, a closure allows a function to retain access to variables from its outer scope even after the outer scope has finished executing.
const outerFunction = () => {
let outerVariable = 'I am from outerFunction';
const innerFunction = () => {
console.log(outerVariable); // Accessing outerVariable from the outer scope
}
return innerFunction; // Returning innerFunction, creating a closure
}
const closure = outerFunction(); // Invoking outerFunction, and storing the returned innerFunction
closure(); // Invoking the innerFunction stored in the closure
13. Uses of Closure
- Encapsulation and Data Privacy
- Module Pattern
- Memoization
- Currying and Partial Application
1. Encapsulation and Data Privacy:
- Closures can be used to create private variables and methods within a function scope, hiding them from the global scope.
- This promotes data privacy and encapsulation, preventing unintended access or modification of variables from outside the function scope.
const createCounter = () => {
let count = 0; // Private variable
return {
increment() {
count++;
console.log(count);
},
reset() {
count = 0;
console.log(count);
}
};
};
const counter = createCounter();
counter.increment(); // Output: 1
counter.increment(); // Output: 2
counter.reset(); // Output: 0
2. Module Pattern:
- Closures are often used to implement the module pattern, allowing you to create modules with private state and expose only the necessary functionality through a public interface.
- Modules help organize code into smaller, more manageable pieces and prevent namespace pollution.
const calculator = (() => {
// Private variable
let result = 0;
// Private function
const add = (num) => {
result += num;
};
// Public interface
return {
add(num) {
add(num);
},
getResult() {
return result;
}
};
})();
calculator.add(5);
calculator.add(10);
console.log(calculator.getResult()); // Output: 15
3.Memoization:
- Closures can be used for memoization, a technique used to cache the results of expensive function calls to improve performance by avoiding redundant computations.
- By storing the results of previous function calls in a closure, subsequent calls with the same input can return the cached result instead of recomputing it.
const memoize = (func) => {
const cache = {};
return (...args) => {
const key = JSON.stringify(args);
if (!(key in cache)) {
cache[key] = func(...args);
}
return cache[key];
};
};
const memoizedAdd = memoize((x, y) => {
console.log('Executing expensive operation...');
return x + y;
});
console.log(memoizedAdd(2, 3)); // Output: Executing expensive operation... 5
console.log(memoizedAdd(2, 3)); // Output: 5 (result fetched from cache)
4.Currying and Partial Application:
- Closures enable currying and partial application, techniques used to transform a function that takes multiple arguments into a series of functions, each taking a single argument.
- This allows for more flexible and reusable code by creating specialized versions of functions with preset arguments.
const multiply = (x) => {
return (y) => {
return x * y;
};
};
const double = multiply(2);
console.log(double(5)); // Output: 10
const triple = multiply(3);
console.log(triple(5)); // Output: 15
14. Currying and partial application
Currying and partial application are functional programming techniques used to transform a function that takes multiple arguments into a series of functions, each taking a single argument. Both techniques facilitate code reuse, composition, and flexibility.
Currying:
- Currying is the process of converting a function with multiple arguments into a sequence of unary (single-argument) functions.
- Each unary function takes one argument and returns another function that takes the next argument until all arguments have been provided, and then the final result is returned.
- Currying enables partial application and allows for more flexible function composition.
Example:
// Original function
const add = (x, y) => x + y;
// Curried version
const curriedAdd = (x) => (y) => x + y;
const addTwo = curriedAdd(2); // Returns a function that adds 2 to its argument
console.log(addTwo(3)); // Output: 5
Partial Application:
- Partial application is the process of fixing a number of arguments to a function, producing a new function with fewer arguments.
- The new function can be invoked later with the remaining arguments.
- Partial application allows for creating specialized versions of functions with preset arguments, promoting code reuse and flexibility.
Example:
// Original function
const greet = (greeting, name) => `${greeting}, ${name}!`;
// Partial application
const greetHello = greet.bind(null, 'Hello'); // Fix the first argument as 'Hello'
console.log(greetHello('Alice')); // Output: Hello, Alice!
console.log(greetHello('Bob')); // Output: Hello, Bob!
Uses of Currying and Partial Application:
Modularization: Currying and partial application allow for breaking down complex functions into smaller, reusable units. Code Flexibility: They enable creating specialized versions of functions with preset arguments, promoting code flexibility and reusability. Functional Composition: Currying and partial application facilitate functional composition by allowing functions to be composed together more easily.
15. Promises
Promises in JavaScript are objects used to represent the eventual completion (or failure) of an asynchronous operation and its resulting value. They allow you to write asynchronous code in a more readable and manageable way, especially when dealing with multiple asynchronous tasks or chaining asynchronous operations.
A promise can be in one of three states:
Pending: Initial state, neither fulfilled nor rejected. Fulfilled (Resolved): The operation completed successfully, and the promise has a value. Rejected: The operation failed, and the promise has a reason for failure.
Here's an example of how promises work in JavaScript:
// Example asynchronous function that returns a promise
const fetchData = () => {
// Simulating an asynchronous operation (e.g., fetching data from an API)
return new Promise((resolve, reject) => {
// Simulating a delay with setTimeout
setTimeout(() => {
// Resolve the promise with some data
resolve('Data fetched successfully');
// Uncomment the line below to simulate a rejection
// reject('Error fetching data');
}, 2000); // Simulate a delay of 2 seconds
});
};
// Using the promise
console.log('Fetching data...');
fetchData()
.then(data => {
console.log('Success:', data); // Output: Success: Data fetched successfully
})
.catch(error => {
console.error('Error:', error); // Output: Error: Error fetching data
});
Chaining Promises
Promises can be chained together using then method. Each then method returns a new promise, allowing you to chain multiple asynchronous operations in a sequential manner.
fetchData()
.then(data => {
console.log('Step 1:', data);
return fetchData(); // Chain another asynchronous operation
})
.then(data => {
console.log('Step 2:', data);
// Perform more operations or return another promise
})
.catch(error => {
console.error('Error:', error);
});
16. Handle multiple promises
-
Promise.all() - is a method that takes an iterable of promises as input and returns a single Promise that resolves when all of the input promises have resolved, or rejects with the reason of the first promise that rejects.
-
Promise.allSettled() - is a method that takes an iterable of promises as input and returns a single Promise that resolves after all of the input promises have either fulfilled or rejected.
-
Promise.race() - is a method that takes an iterable of promises as input and returns a single Promise that resolves or rejects as soon as one of the input promises resolves or rejects.
-
Promise.any() - is a method that takes an iterable of promises as input and returns a single Promise that resolves as soon as one of the input promises fulfills, or rejects if all of the input promises reject.
1. Promise.all()
- This method takes an array of promises and returns a single promise.
- The returned promise fulfills when all the input promises have fulfilled, or rejects with the reason of the first promise that rejects.
- ALL PROMISES MUST BE RESOLVED FOR IT TO RETURN A RESPONSE
const promise1 = new Promise((resolve) =>
setTimeout(() => resolve("Promise 1 resolved"), 1000)
);
const promise2 = new Promise((resolve) =>
setTimeout(() => resolve("Promise 2 resolved"), 500)
);
const promise3 = new Promise((resolve) =>
setTimeout(() => resolve("Promise 3 resolved"), 800)
);
const promisesArray = [promise1, promise2, promise3];
Promise.all(promisesArray)
.then((results) => {
console.log("All promises resolved:", results);
})
.catch((error) => {
console.error("At least one promise rejected:", error);
});
OUTPUT
All promises resolved: [ 'Promise 1 resolved', 'Promise 2 resolved', 'Promise 3 resolved' ]
If promise3 fails
At least one promise rejected: Promise 3 rejected
2. Promise.allSettled()
- The Promise.allSettled method is used to handle multiple promises concurrently, just like Promise.all, but it waits for all the promises to settle (either resolve or reject) before proceeding.
- It returns an array of objects representing the outcomes of the input promises, including their values or reasons for rejection.
- Promise.allSettled() always returns array of objects with status key which denotes fulfilled or rejected.
- If a promise is fulfilled then you can get response with value key and if the promise is rejected then you can find the reason in reason key.
const promisesArray = [promise1, promise2, promise3];
Promise.allSettled(promisesArray).then((results) => {
console.log("All promises settled:", results);
});
OUTPUT
// SUCCESS
[
{ status: 'fulfilled', value: 'Promise 1 resolved' },
{ status: 'fulfilled', value: 'Promise 2 resolved' },
{ status: 'fulfilled', value: 'Promise 3 resolved' }
]
// Promise2 rejected
[
{ status: 'fulfilled', value: 'Promise 1 resolved' },
{ status: 'rejected', reason: 'Promise 2 resolved' },
{ status: 'fulfilled', value: 'Promise 3 resolved' }
]
The Promise.all and Promise.allSettled methods are both used to work with multiple promises, but they have different behaviors when it comes to handling resolved and rejected promises. Here are the key differences between the two methods:
Handling Rejected Promises:
Promise.all: If any of the input promises is rejected, the entire returned promise will immediately reject with the reason of the first rejected promise. The resolution of Promise.all depends on all promises being fulfilled successfully.
Promise.allSettled: The returned promise always resolves, regardless of whether individual promises were fulfilled or rejected. The result is an array of objects representing the outcomes of all input promises, including both fulfilled and rejected ones.
3. Promise.race()
-
As the name suggests, race returns first promise with shortest delay whether it is resolved or rejected.
-
For example if there are 5 promises which returns result like this: Promise 1 ==> 1 second (rejected) Promise 2 ==> 2 seconds (rejected) Promise 3 ==> 3 seconds (resolved) Promise 4 ==> 4 seconds (resolved) Promise 5 ==> 5 seconds (resolved)
-
So, it will return us Promise 1 because it was the first one being returned.
-
The Promise.race method in JavaScript is used to handle multiple promises concurrently, but it resolves or rejects as soon as the first promise in the input array settles, either by resolving or rejecting.
-
This can be useful when you're interested in the result of the first promise to complete, regardless of whether it's a success or failure.
Promise.race(promisesArray)
.then((results) => {
console.log("First promise resolved:", results);
})
.catch((error) => {
console.error("At least one promise rejected:", error);
});
// Result
First promise resolved: Promise 2 resolved
// If Rejected
At least one promise rejected: Promise 2 rejected
4. Promise.any()
It is somewhat similar to race method but with few minor differences:
It will return with first resolved promise.
For example if there are 5 promises which returns result like this:
Promise 1 ==> 1 second (rejected)
Promise 2 ==> 2 seconds (rejected)
Promise 3 ==> 3 seconds (resolved)
Promise 4 ==> 4 seconds (resolved)
Promise 5 ==> 5 seconds (resolved)
So, it will return us Promise 3 because it was the first one being resolved.
First promise resolved: Promise 3 resolved
If all promises are rejected, it will give you an aggregated error. For example if there are 5 promises which returns result like this:
Promise 1 ==> 1 second (rejected)
Promise 2 ==> 2 seconds (rejected)
Promise 3 ==> 3 seconds (rejected)
Promise 4 ==> 4 seconds (rejected)
Promise 5 ==> 5 seconds (rejected)
So, now it will return an aggregated result like this:
[AggregateError: All promises were rejected]
Differences
Rejection Handling: Promise.race: If the promise that settles first is rejected, the returned promise will also reject with the same rejection reason. Promise.any: If all input promises reject, the returned promise will reject with an aggregated error containing the reasons of all rejected promises. It only resolves if at least one promise resolves.
Use Cases: Promise.race: Useful when you want to implement scenarios like a timeout mechanism where you want to respond to the first promise to complete, regardless of whether it succeeds or fails. Promise.any: Useful when you want to handle the case where at least one promise out of multiple promises succeeds, and you're interested in the result of the first resolving promise.