JavaScript is single threaded, that means only one statement is executed at a time. As the JS engine processes our script line by line, it uses this single Call-Stack to keep track of codes that are supposed to run in their respective order.
Like what a stack does, a data structure which records lines of executable instructions and executes them in LIFO manner. So say if the engine steps into a function foo(){ it PUSH-es foo() into the stack and when the execution of foo()return; } is over foo() is POP-ped out of the call-stack.
EXERCISE 1: So from the above diagram shows how a typical line by line execution happens. When the script of three console.log() statements is thrown at JS —
-
Step 1: The console.log(“Print 1”) is pushed into the call stack and executed, once done with execution, it is then popped out of the stack. Now the stack is empty and ready for any next instruction to be executed.
-
Step 2: console.log(“Print 2”); // is the next instruction is pushed and the same thing repeats until
-
Step 3: is executed and there is nothing left to push and execute.
Lets take an example to understand it better, its a famous Interview question as well. In the logText() function, I am only doing console.log() three times.
const logText = () => {
console.log('A');
setTimeout(() => {
console.log('B');
}, 0);
console.log('C');
}
logText()
The output A C B
Explanation – Even though, I am passing 0 to the setTimeout() for the waiting period, because setTimeout() invokes a callback function – this callback function will be passed to an Event Loop. And we we know that – The event loop as a process is a set of phases with specific tasks that are processed in a round-robin manner – so the callback function will go through those phases and ultimately get executed. But during this process Node being Node, will be non-blocking, and so would NOT block the next lines of code which is the third line < console.log(‘C’) >
Further Reading
Worker pool is an execution model that spawns and handles separate threads, which then synchronously perform the task and return the result to the event loop. The event loop then executes the provided callback with said result.
Problem Statement – I want to fire second request based on one value returned by the first request. So, axios.all will not resolve my probelem.
I can use async and await
async function getData() {
const firstRequest = await axios.get(`${<URL1>}`);
data1 = firstRequest.data[0];
if (data1){
const secondRequest = await axios.get(`${<URL2>}`);
data1 = secondRequest.data;
}
return data1;
}
A very important use case of this is, when resetting password. Before resetting the password, I have to make sure, that the unique-link sent to the user is valid (Not expired and the same that was sent to him) – So, for this, in the same password-reset function, I have to first make an axios call to an API endpoint, which will validate that the link sent to the user is valid. And only if this API call returns a valid respoonse, I will fire the second API call to the endpoint to actually reset the password.
Extract from a PasswordReset Component production grade project below
componentDidMount() {
const parsed = queryString.parse(this.props.location.search);
this.setState({
emailFromURLParams: parsed.email,
uidFromURLParams: parsed.uid
});
}
// Function to reset the password - It will fire two sequential axios request and based on first request's successful response value, will do the next axios request.
passwordResetOnSubmit = async e => {
e.preventDefault();
const {
emailFromURLParams,
uidFromURLParams,
newResetPassword,
confirmNewPassword
} = this.state;
const firstRequest = await axios
.post("/api/forgot-password/verify-uid-before-password-reset", {
emailFromURLParams,
uidFromURLParams
})
.catch(error => {
if (error.response.status === 400) {
this.setState({ openWrongDateRangeSnackBar: true });
}
});
const userId = await (firstRequest && firstRequest.data);
if (userId && newResetPassword === confirmNewPassword) {
axios
.put("/api/user/password-reset", {
userId,
newResetPassword
})
.then(() => {
this.setState(
{
openNewItemAddedConfirmSnackbar: true,
newResetPassword: "",
confirmNewPassword: ""
},
() => {
setInterval(function() {
history.push("/login");
}, 3000);
}
);
})
.catch(error => {
if (error.response.status === 404) {
this.setState({
message: "Authentication failed. User not found."
});
} else if (error.response.status === 401) {
this.setState({ message: "Authentication failed. Wrong Password" });
} else if (
error.response.status === 500 ||
error.response.status === 422
) {
this.setState({
message:
"Authentication failed. Only for Port authorized personnel"
});
}
});
}
};
Use case (Sequential Execution with plain callback function and async-await) - Here in the below EDIT / PUT route - From Client side req.body, I am sending a JSON data which along with other info has a 'date' field. I had to format that 'date' from req.body (to "YYYY-MM-DD") with moment, before running findByIdAndUpdate(). Else mongoose was saving the date one day prior to what I was selecting in the DatePicker, and then subsequent API call for that date's data (after the EDIT) will NOT give me the correct edited data.
NOTE - The code is working EVEN WITHOUT async-await. But including async-await seems to be better for safely achieving the result.
req.body is as below
{
"wharfage": 143,
"berth_hire": 5,
"other_services": 6,
"port_dues": 20,
"total": 26,
"date": "2019-02-25"
}
The actual route code in Express backend
Alternative – 1 – With a wrapper function which will invoke a callback()
router.put("/:id", (req, res, next) => {
let editedItem = req.body;
// Have to do this extra date formatting step, to change the formatting of the 'date' field. Else mongoose was saving the date one day prior to what I was selecting in the DatePicker.
// Wrapper function that will wrap the database call (the findByIdAndUpdate() query) within a callback function that gets executed after the database query has finished.
const wrapperFunction = async (editItem, callback) => {
editItem.date = await moment(editItem.date).format("YYYY-MM-DD");
if (typeof callback === "function") {
callback();
}
};
wrapperFunction(editedItem, () => {
Revenue.findByIdAndUpdate(req.params.id, editedItem, { new: true }).exec(
(err, record) => {
if (err) {
console.log(err);
return next(err);
} else {
res.status(200).json(record);
}
}
);
});
});
Alternative – 2 for the above EDIT (PUT) route function (with an extra database call ) – this code was working
router.put("/:id", (req, res, next) => {
let editedItem = req.body;
Revenue.findById(req.params.id, (err, result) => {
if (err) {
console.log(err);
} else {
editedItem.date = moment(editedItem.date).format("YYYY-MM-DD");
}
}).then(() => {
Revenue.findByIdAndUpdate(req.params.id, editedItem, { new: true }).exec(
(err, record) => {
if (err) {
console.log(err);
return next(err);
} else {
res.status(200).json(record);
}
}
);
});
});
Alternative-2 for the EDIT (PUT) route function above – Here, I am just plain formatting the date without resorting to any technique to make the next db-call function to wait. While its working the solution with async-await seems to be better for safely achieving the same.
router.put("/:id", (req, res, next) => {
let editedItem = req.body;
editedItem.date = moment(editedItem.date).format("YYYY-MM-DD");
Revenue.findByIdAndUpdate(req.params.id, editedItem, { new: true }).exec(
(err, record) => {
if (err) {
console.log(err);
return next(err);
} else {
res.status(200).json(record);
}
}
);
});
A specific Use Case –
Here in the below backend Express Route – I have to capture visitor data (after OTP was sent and OTP Mongodb schema updated with that latest OTP) when a visitor to the site downloads data.
But I need to first pick the latest OTP (that was generated and saved in mongodb) from the already saved database before comparing with the otp input by the user when prompted.
router.route("/visitor/:id").put((req, res, next) => {
let visitorData = req.body;
let visitorEmail = req.body.company_email;
let latestOtp = [];
// Wrapper function that will wrap the database call (the find() query) in a function and pass it a callback function that gets executed after the database query has finished.
// Also always make sure the callback is indeed a function, without this check, if the findLatestOTP() is called either without the callback function as a parameter OR in place of a function a non-function is passed, our code will result in a runtime error.
function findLatestOTP(mongoCollection, callback) {
mongoCollection
.find({ visitor_email: visitorEmail })
.limit(1)
.sort({ createdAt: -1 })
.exec((err, record) => {
if (err) {
console.log(err);
} else {
latestOtp.push(record[0].generated_otp);
if (typeof callback === "function") {
callback();
}
}
});
}
findLatestOTP(OTP, function() {
if (req.body.otpReceivedByVisitor !== latestOtp[0]) {
return res
.status(401)
.send({ success: false, msg: "Incorrect Code was input" });
} else {
DOCUMENTMODAL.findById(req.params.id, (err, record) => {
if (err) {
console.log(err);
}
record.visitor.push(visitorData);
record.save((err, updatedRecord) => {
if (err) {
return next(err);
}
res.status(200).send(updatedRecord);
});
});
}
});
});
Some related Explanation –
JavaScript is single threaded, that means only one statement is executed at a time. As the JS engine processes our script line by line, it uses this single Call-Stack to keep track of codes that are supposed to run in their respective order.
Like what a stack does, a data structure which records lines of executable instructions and executes them in LIFO manner. So say if the engine steps into a function foo(){ it PUSH-es foo() into the stack and when the execution of foo()return; } is over foo() is POP-ped out of the call-stack.
