console.log("start")
for (let i=0; i < 6; i+=1) {
console.log({i})
}
console.log("end")
// Output
/**
* start
* 0
* 1
* 2
* 3
* 4
* 5
* end
* */
console.log("start")
for (let i=0; i < 6; i+=1) {
console.log({i})
}
console.log("end")
// Output
/**
* start
* 0
* 1
* 2
* 3
* 4
* 5
* end
* */
for_loop
written here takes a lot of time to complete?console.log("end")
from executing.for_loop
or to put the for_loop
on a side-thread and execute the next code statement?const users = [
"asdas1wwq23wd3",
"wwq23wd3wwq23wd3",
"q23wd3wwq23wd3ww" ] // ...100_000 more
const authenticatedUser = "qaaqq13oopf233ww";
let currentUser; // undefined
for (let i=0; i < 6; i+=1) {
if (users[i] === authenticatedUser) {
currentUser = users[i]
}
}
console.log(currentUser)
// Output
// undefined
for_loop
on a side thread and moved on to execute the console.log("end"). It worked fine.for_loop
? How will we go ahead by side threading the for_loop?const users = [
"asdas1wwq23wd3",
"wwq23wd3wwq23wd3",
"q23wd3wwq23wd3ww" ] // ...100_000 more
const authenticatedUser = "qaaqq13oopf233ww";
let currentUser; // undefined
for (let i=0; i < 6; i+=1) {
if (users[i] === authenticatedUser) {
currentUser = users[i]
}
}
console.log(currentUser)
// Output
// undefined
const users = [
"asdas1wwq23wd3",
"wwq23wd3wwq23wd3",
"q23wd3wwq23wd3ww" ] // ...100_000 more
const authenticatedUser = "qaaqq13oopf233ww";
function getCurrentUser(data, callback) {
const { users, authenticatedUser } = data
let currentUser; // undefined
for (let i=0; i < 6; i+=1) {
if (users[i] === authenticatedUser) {
currentUser = users[i]
}
}
if (currentUser) {
callback(currentUser)
}
}
getCurrentUser({users, authenticatedUser}, (err, data) => {
console.log(data)
})
You can try 3 things here:
for_loop
inside the promise. And when you consume the promise, put your console statement there.Promises:
The concept of using promises has 2 steps to it:
Creating Promise:
The promise constructor, receives 1 argument, an executor function.
This executor function also takes 2 arguments, both of them functions:
resolve, and reject
const myPromise = new Promise(() => {});
const myPromise = new Promise((resolve, reject) => {});
const promise = new Promise(
(resolve, reject) => {
const num = Math.random()*100
if (num > 200){
resolve("done")
} else {
reject("")
}
});
A promise is always in one the following states:
Consuming a Promise:
Matching resolve, and reject with .then and .catch
const parameter = "success"
const myPromise = new Promise((resolve, reject) => {
// Simulating an asynchronous operation
setTimeout(() => {
if (parameter === 'success') {
resolve('Operation succeeded');
} else {
reject(new Error('Operation failed'));
}
}, 1000); // Simulating 1 second delay
});
// consuming a promise
myPromise
.then(result => {
console.log('Success:', result);
})
.catch(error => {
console.error('Error:', error.message);
})
.finally(() => {
console.log(`This will be executed regardless of
success or failure.`);
});
Promise has 2 states (resolve, reject) because:
in case the code you have written throws some error, the promises goes in the rejected state.
Using .finally:
const parameter = "success"
const myPromise = new Promise((resolve, reject) => {
// Simulating an asynchronous operation
setTimeout(() => {
if (parameter === 'success') {
resolve('Operation succeeded');
} else {
reject(new Error('Operation failed'));
}
}, 1000); // Simulating 1 second delay
});
// consuming a promise
myPromise
.then(result => {
console.log('Success:', result);
})
.catch(error => {
console.error('Error:', error.message);
})
.finally(() => {
console.log('This will be executed regardless of success or failure.');
});
Practically using Promises:
function myAsyncFunction(parameter) {
return new Promise((resolve, reject) => {
// Simulating an asynchronous operation
setTimeout(() => {
if (parameter === 'success') {
resolve('Operation succeeded');
} else {
reject(new Error('Operation failed'));
}
}, 1000); // Simulating 1 second delay
});
}
myAsyncFunction('success')
.then(result => {
console.log('Success:', result);
})
.catch(error => {
console.error('Error:', error.message);
})
.finally(() => {
console.log(`This will be executed
regardless of success or failure.`);
});
But why do we need to wrap Promises inside a function:
new Promise((res, rej) => {})
/*
The Promise constructor gets called,
It constructs a Promise object,
And returns it to the 'myPromise' variable
In order to contruct the Promise object,
the constructor has to execute any JS code
passed to it (through the executor function)
*/
const myPromise = new Promise((resolve, reject) => {
const results = [];
for (let i = 0; i < 10_000; i++) {
console.log({i})
results.push(i)
}
resolve(results)
});
myPromise
.then(x => console.log(x))
Practically using Promises:
fetch("https://jsonplaceholder.typicode.com/posts")
.then(response => {
return response.json()
})
.then(jsonData => {
console.log({jsonData})
return jsonData
})
.then(data => {
console.log({data})
return fetch("https://jsonplaceholder.typicode.com/todos/1")
})
.then(todoData => {
return todoData.json()
})
.catch(error => {
console.error('Error:', error.message);
})
.finally(() => {
console.log(`This will be executed
regardless of success or failure.`);
});
.then(todoJsonData => {
console.log({todoJsonData})
})
Practically using Promises:
doSomething()
.then((url) => {
// Missing `return` keyword in front of fetch(url).
fetch(url);
})
.then((result) => {
// result is undefined,
// because nothing is returned from the previous
// handler. There's no way to know
// the return value of the fetch()
// call anymore, or whether it succeeded at all.
});
doSomething()
.then((result) => {
return doSomethingElse(result);
})
.then((newResult) => {
return doThirdThing(newResult);
})
.then((finalResult) => {
console.log(`Got the
final result: ${finalResult}`);
})
.catch(failureCallback);
function asyncOperation(success) {
return new Promise((resolve, reject) => {
if (success) {
// Resolving the promise
resolve("completed");
} else {
// Rejecting the promise
reject(new Error("failed"));
}
// Attempting to resolve/reject
// the promise again will have no effect
resolve("Won't Run");
reject(new Error("Won't run"));
});
}
// Resolving example
asyncOperation(true)
.then((result) => {
console.log("Resolved:", result);
})
.catch((error) => {
console.error("Error:", error.message);
});
Practically using Promises:
A promise can be "fulfilled" only once, with either resolve or reject
Eg: You cannot write a loop with resolve or reject, inside a Promise.
function asyncOperation() {
return new Promise((resolve, reject) => {
const results = [];
// Simulating a for loop inside
// the promise executor function
for (let i = 0; i < 5; i++) {
resolve(i);
}
});
}
Practically using Promises:
A promise can be "fulfilled" only once, with either resolve or reject
Eg:
You cannot write a loop with resolve or reject, inside a Promise.
const prom = new Promise((resolve, reject) => {
const results = [];
for (let i = 0; i < 10_000; i++) {
console.log({i})
results.push(i)
}
resolve(results)
});
prom
.then(data => console.log({data}))
prom
.then(data => console.log({data}))
prom
.then(data => console.log({data}))
function asyncOperation() {
return new Promise((resolve, reject) => {
const results = [];
for (let i = 0; i < 10_000; i++) {
console.log({i})
results.push(i)
}
resolve(results)
});
}
Practically using Promises:
If a promise is once resolved (made an API call), it won't have to resolve again. If you again consume the promise, it will give the exact same result (won't call the API again).
Note: This doesn't apply if you have wrapped a Promise inside a function.
const promisified = (api) => {
return new Promise((res, rej) => {
const getResult = callApiToGetResult(api)
if ( getResult ) {
res(getResultJson)
}
rej("Rejected")
})
}
promisified("https://result.api/")
.then(() => {}, () => {})
// Can be converted to an async function:
const promisified = async (api) => {
const getResult = callApiToGetResult(api)
return getResult
}
// calling promisified() because its a function
promisified("https://result.api/")
.then(() => {}, () => {})
const promisified = (api) => {
return new Promise((res, rej) => {
const getResult = callApiToGetResult(api)
if ( getResult ) {
res(getResultJson)
}
rej("Rejected")
})
}
promisified("https://result.api/")
.then(() => {}, () => {})
// Can be converted to an async function:
const promisified = async (api) => {
const getResult = callApiToGetResult(api)
return getResult
}
// calling promisified() because its a function
promisified("https://result.api/")
.then(() => {}, () => {})
When consuming a promise, the .then doesn't run un till the resolve inside the promise is called.
await stops the code at that line, and moves to the next line only after that line has executed completely i.e only when the promise has resolved.
await can only be used inside an async function, because only inside a promise you'd want to write any blocking code.
// Can be converted to an async function:
const promisified = async (api) => {
const getResult = await fetch(api)
const getResultJson = await getResult.json()
return getResultJson
}
// calling promisified() because its a function
promisified("https://result.api/")
.then((resp) => {
console.log({resp})
})
.catch((error) => {
console.error({error})
})
async function getDataPromise(API){
const data = await fetch(API)
const jsonData = await data.json()
return jsonData;
})
}
const API = 'https://jsonplaceholder.typicode.com/posts'
async function finalDataFunction(API){
const res = await getDataPromise(API)
console.log({res})
}
finalDataFunction(API)
function getDataPromise(API){
return new Promise((res, rej) => {
fetch(API)
.then(data => data.json())
.then(jsonData => res(jsonData))
})
}
const API = 'https://jsonplaceholder.typicode.com/posts'
getDataPromise(API)
.then(res => console.log({res}})
Error Handling in Promises:
doSomething() // if error
.then((result) => doSomethingElse(result)) // won't run
.then((newResult) => doThirdThing(newResult)) // won't run
.then((finalResult) => console.log({finalResult}) // won't run
.catch(err => console.log({err})) // Runs to throw error
.then((newResult) => console.log('1')) // runs anyway
.then((newResult) => console.log('2')) // runs anyway
// Case for having multiple .catch in the chain
doSomething() // if error
.then((result) => doSomethingElse(result)) // won't run
.then((newResult) => doThirdThing(newResult)) // won't run
.then((finalResult) => console.log({finalResult}) // won't run
.catch(err => console.log({err})) // Runs to throw error
.then((newResult) => console.log('1')) // runs anyway
.then((newResult) => console.log('2')) // runs anyway
.catch(err => console.log({err}))
// Runs if error in 1 of above 2 .thens
Error Handling in Promises:
doSomething()
.then(resp => console.log({resp})
.catch(err => console.log({err}))
// can be written as
doSomething()
.then(() => {}, () => {})
Callback Queue: For setTimeout, setIntervals etc
JavaScript has a runtime model based on an event loop, which is responsible for executing the code, collecting and processing events, and executing queued sub-tasks.
This model is quite different from models in other languages like C and Java.
Ref: https://dev.to/lydiahallie/javascript-visualized-event-loop-3dif
Promise.resolve()
Promise.reject()
Promise.all()
Promise.allSettled()
Promise.any()
Promise.race()
Direct resolve and reject:
const resolvedPromise = Promise.resolve("Resolved value");
resolvedPromise.then(value => {
console.log(value); // Output: Resolved value
});
const rejectedPromise = Promise.reject(new Error("Rejected promise"));
rejectedPromise.catch(error => {
console.error(error.message); // Output: Rejected promise
});
Promise.all: (all or nothing)
const urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://api.github.com/users/jeresig'
];
// map every url to the promise of the fetch
const requests = urls.map(url => fetch(url));
// Promise.all waits until all jobs are resolved
Promise.all(requests)
.then(responses => responses.forEach(
response => alert(`${response.url}: ${response.status}`)
));
const promise1 = Promise.resolve("Promise 1 resolved");
const promise2 = Promise.reject("Rejected promise");
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, "Promise 3 resolved");
});
Promise.all([promise1, promise2, promise3]).then(values => {
console.log(values); // Output: ["Promise 1 resolved", 42, "Promise 3 resolved"]
});
Promise.allSettled
similar to Promise.all, but it doesn't stop at first rejected promise,
just waits for all promises to settle, regardless of the result.
The resulting array has:
{status:"fulfilled", value:result}
for successful responses,
{status:"rejected", reason:error}
for errors.
Order of output array remains same as input array,
const promise1 = Promise.resolve("Resolved");
const promise2 = Promise.reject(new Error("Rejected"));
Promise.allSettled([promise1, promise2]).then(results => {
console.log(results);
/* Output:
[
{ status: "fulfilled", value: "Resolved" },
{ status: "rejected", reason: Error: Rejected }
]
*/
});
// [
// {status: 'fulfilled', value: ...response...},
// {status: 'fulfilled', value: ...response...},
// {status: 'rejected', reason: ...error object...}
// ]
Promise.race
Promise.race([
new Promise((res, rej) => setTimeout(() => res(1), 1000)),
new Promise((res, rej) => setTimeout(() => rej(new Error("Whoops!")), 2000)),
new Promise((res, rej) => setTimeout(() => res(3), 3000))
])
.then(alert); // 1
Promise.any
Promise.any([
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 1000)),
new Promise((resolve, reject) => setTimeout(() => resolve(1), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
])
.then(alert); // 1
AbortController: Aborting Promises:
AbortController can be used to abort/stop any ongoing asynchronous task (Promise), including fetch as well.
Using an AbortController object is very straightforward:
// Step 1. Create a new AbortController Object
let controller = new AbortController();
// controller = {
// signal: {},
// abort: () => {}
// }
// call the abort method from where you want to abort
// the ongoing request
controller.abort(); // abort!
// use the signal property to cancel any ongoing request
let signal = controller.signal;
signal.addEventListener('abort', () => {
alert("abort!") //. whatever you want to do when the
// request is cancelled!
});
// The event triggers and signal.aborted becomes true
alert(signal.aborted); // true
AbortController: Aborting Promises:
Using AbortController to cancel an ongoing fetch request:
To be able to cancel a fetch, pass the signal property of an AbortController as a fetch option:
let controller = new AbortController();
fetch(url, {
// pass signal to fetch, in the options object
signal: controller.signal
});
// call the abort method when
// you want to cancel request
controller.abort();
// abort in 1 second
let controller = new AbortController();
// calling abort after 1s
setTimeout(() => controller.abort(), 1000);
try {
let response = await fetch('https://jsonplaceholder.typicode.com/posts', {
// passing signal to fetch
signal: controller.signal
});
} catch(err) {
if (err.name == 'AbortError') { // handle abort()
alert("Aborted!");
} else {
throw err;
}
}
Note: When a fetch is aborted, its promise rejects with an error AbortError, so we can handle it, e.g. in a try..catch or a .then/.catch
const urls = [...]; // a list of urls to fetch in parallel
const controller = new AbortController();
// an array of fetch promises
const fetchJobs = urls.map(url => fetch(url, {
signal: controller.signal
}));
const results = await Promise.all(fetchJobs);
// if controller.abort() is called from anywhere,
// it aborts all fetches
Note: AbortController allows to cancel multiple promises/fetch requests at once as well.
Create a new AbortController, and pass the same signal object to all the promises/fetch requests.
When you call the abort() method, it cancels all the promises having that signal.
const controller = new AbortController();
// our task
const ourJob = new Promise((resolve, reject) => {
...
// Some async code executing
...
controller
.signal
.addEventListener('abort', () => reject("Abort called"));
});
// calling abort after 1s
setTimeout(() => controller.abort(), 1000);
// Wait for fetches and our task in parallel
Note: Aborting a simple promise using AbortController
const debounceFunc = (func, delay) => {
let timer;
return function(...args) {
const context = this;
clearTimeOut(timer);
timer = setTimeOut(() => {
func.apply(context, args);
}, delay)
}
}
const optimisedSearchHandler = debounceFunc(searchHandler, 500)
Debouncing enforces that a function not be called again until a certain amount of time has passed without it being called.
As in “execute this function only if 100 milliseconds have passed without it being called.”
Only the last call made in a time frame will go through.
const throttleFunc = (func, interval) => {
let shouldFire = true;
return function() {
if (shouldFire) {
func();
shouldFire = false;
setTimeOut(() => {
shouldFire = true;
}, interval)
}
}
}
const optimisedTriggerHandler = throttleFunc(handlerTrigger, 100);
Throttling enforces a maximum number of times a function can be called over time. As in “execute this function at most once every 100 milliseconds.”
All the calls will go through, but only after fixed time interval