Javascript Interview Questions (Part 2)

Javascript Interview Questions (Part 2)

Hello my friends, I know it's been a while since the last time I wrote something on my blog. Hmm, that rings a bell, right? Maybe because I've been too lazy these days and I just don't know what topic or what content should I bring to the table next. I think that I'm living in a self-indulgent way and I'm trying to get out of it.

Okay, it's enough for the sideline, today, I will come back to the series Javascript Interview Questions. Actually, I struggled to think about what's kind of questions or answers I should put into it. Okay, I don't want to waste your time anymore, let's dive into it now.

I mentioned a way to deep copy an object in the previous post. But at that time I didn't mention rick (to be honest, I didn't know about that risk yet. Yeah every trick comes with a cost). This is a question which I was asked right after the question "how to deep clone an object" and I failed to answer this question.

Deep copy an object using the trick:

jsx
const cloned = JSON.parse(JSON.stringify(original));
const cloned = JSON.parse(JSON.stringify(original));

By doing this, any property which doesn't have an equivalent type in JSON you will be lost. Moreover, every property that has value undefined will be ignored by JSON.stringify which leads to a lack of properties in cloned objects. Also, some objects are converted to strings, like Date objects for example (also, not taking into account the timezone and defaulting to UTC), Set, Map, and many others:

For example:

jsx
JSON.parse(
  JSON.stringify({
    a: new Date(),
    b: NaN,
    c: new Function(),
    d: undefined,
    e: function () {},
    f: Number,
    g: false,
    h: Infinity,
  }),
);
JSON.parse(
  JSON.stringify({
    a: new Date(),
    b: NaN,
    c: new Function(),
    d: undefined,
    e: function () {},
    f: Number,
    g: false,
    h: Infinity,
  }),
);

This is the result:

Output of JSON.parse(JSON.stringify(...)) after cloning an object with Date, NaN, functions, undefined, and other non-JSON values

One more disadvantage of using this trick is that it's extremely slow. It can affect the performance of your website if you overuse it.

So now the question is: What's the best way of deep cloning an object.

Well, we have no way except to use an external library like lodash or writing our function. But if you just have an object with a simple type of key/value, I think it would be fine to use this trick.

I have a setTimeout, how could I enforce the program to "wait" for setTimeout to finish before continue executing?

For example I have this kind of scenario:

jsx
// call api
// when api returns result, wait for 2 seconds before showing the result?
// (using setTimeout)
// call api
// when api returns result, wait for 2 seconds before showing the result?
// (using setTimeout)

With this case, we can use a promise-based solution like this:

jsx
const delay = (time: number) => new Promise(res => setTimeout(res, time));

const showData = async () => {
	const res = await callapi();
	await delay(2000);
	return res;
}
const delay = (time: number) => new Promise(res => setTimeout(res, time));

const showData = async () => {
	const res = await callapi();
	await delay(2000);
	return res;
}

I have multiple requests and want to wait for all of them to be finished before going to next step? How many ways I can do to archive this? Compare them if we have multi ways.

At this point, I have two ways to archive that goal. Promise all and await loop

Promise.all will create all promises before waiting for them to resolve which means your requests will be fired concurrently and the callback function will only be executed when all of your promises resolve successfully.

jsx
const promise1 = Promise.resolve("1");
const promise2 = 2;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, "3");
});

// Promise.all
Promise.all([promise1, promise2, promise3]).then((values) => {
  console.log(values);
});
// expected output: ["1", 2, "3"]
const promise1 = Promise.resolve("1");
const promise2 = 2;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, "3");
});

// Promise.all
Promise.all([promise1, promise2, promise3]).then((values) => {
  console.log(values);
});
// expected output: ["1", 2, "3"]

Await loop will wait for each promise to be resolved before going to the next promise.

Example:

jsx
const promise1 = Promise.resolve("1");
const promise2 = 2;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, "3");
});

// for loop
(async () => {
  for (const promise of [promise1, promise2, promise3]) {
    try {
      const result = await promise;
      console.log(result);
    } catch (error) {
      console.log(error);
    }
  }
})();
// expected output:
// 1
// 2
// 3
const promise1 = Promise.resolve("1");
const promise2 = 2;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, "3");
});

// for loop
(async () => {
  for (const promise of [promise1, promise2, promise3]) {
    try {
      const result = await promise;
      console.log(result);
    } catch (error) {
      console.log(error);
    }
  }
})();
// expected output:
// 1
// 2
// 3

There are 2 main things that we have to notice about the above solutions.

  1. What if a promise is rejected?
  • Promise. all are rejected immediately no matter how many promises are left or how many were promise resolved. And the rejected value will be the one that comes from the rejected promise.
jsx
const promise1 = Promise.resolve("1");
const promise2 = 2;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("reject")), 100);
});

// Promise.all
Promise.all([promise1, promise2, promise3])
  .then((values) => {
    console.log(values);
  })
  .catch((error) => {
    console.log(`err`, error);
  });
// Output: err Error: reject
const promise1 = Promise.resolve("1");
const promise2 = 2;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("reject")), 100);
});

// Promise.all
Promise.all([promise1, promise2, promise3])
  .then((values) => {
    console.log(values);
  })
  .catch((error) => {
    console.log(`err`, error);
  });
// Output: err Error: reject
  • If a promise rejects and we wrapped in a try catch, Await loop won't' stop,it will continue to next promise. But if we don't wrap with try...catch, Await loop will stop immediately.
jsx
const promise1 = Promise.resolve("1");
const promise2 = 2;
const promise3 = new Promise((resolve, reject) => {
  reject(new Error("3"));
});

// for loop
(async () => {
  for (const promise of [promise3, promise1, promise2]) {
    try {
      const result = await promise;
      console.log(result);
    } catch (error) {
      console.log(error);
    }
  }
})();

// Output:
// error Error: 3
// 1
// 2
const promise1 = Promise.resolve("1");
const promise2 = 2;
const promise3 = new Promise((resolve, reject) => {
  reject(new Error("3"));
});

// for loop
(async () => {
  for (const promise of [promise3, promise1, promise2]) {
    try {
      const result = await promise;
      console.log(result);
    } catch (error) {
      console.log(error);
    }
  }
})();

// Output:
// error Error: 3
// 1
// 2

b. The finish time.

Before going to this, I have to clarify in real life there are so many other factors that affect our program so the formula to calculate the finish time of these 2 ways is just in theory

  • With Promise. all, the time we get the callback function to be called is the time that the longest promise resolves. So we can write the complexity of Promise. all is O(longestRequestTime).
  • About Await loop, the complexity would be O(sumOfAllRequestTime). It's reasonable since all promises resolve one by one.

Here is the picture that I think can help you:

https://stackoverflow.com/questions/53798589/await-loop-vs-promise-all

So Promise. all seems to be better if all promise's able to resolve (because it's would be faster in theory) , but will not good if we have any promise rejects (since it will stop immediately and ignore all other promises).

It hard to say because Javascript is magic. Technically, Javascript is pass-by-value.

But we have some cases like this one:

jsx
const someFun = (a, b, c, key, value) => {
  a = 10;
  b[key] = value;
  c = { [key]: value };
};

const num = 20;
const obj1 = { test: "abc" };
const obj2 = { test: "xyz" };

somFunc(num, obj1, obj2, test, "lol");

console.log(num, obj1, obj2);
// Result:
// 20 { test: 'lol' } { test: 'xyz' }
const someFun = (a, b, c, key, value) => {
  a = 10;
  b[key] = value;
  c = { [key]: value };
};

const num = 20;
const obj1 = { test: "abc" };
const obj2 = { test: "xyz" };

somFunc(num, obj1, obj2, test, "lol");

console.log(num, obj1, obj2);
// Result:
// 20 { test: 'lol' } { test: 'xyz' }

As you can see, if we modify the value of variable num (which is a primitive type variable), nothing changes. But interestingly, the value of the test in obj1 has been changed. If Javascript is pass-by-value, how could this thing happen? And what about the behavior with obj2, it's still the same as before?

Well, about this kind of weird thing, we have to understand variables in Javascript. Javascript is pass-by-value, it's truth. But the point here is, with the arguments passed somFunc which are not a primitive type, they are also references! So if you try to change the entire argument passed to function (by assigning it to another object for example), it won't affect that argument, but if we try modifying its internal values, that will affect that argument.

  • when a variable is declared with no initial value. And if we try to access that variable before assigning value to it, we will get "undefined". And that variable at that time will have type of "undefined"
  • About null, it means the value of that variable is null. That variable is declared and assigned to "null" or after some manipulation with that variable, it's set to "null". Type of "null" is "object".

So if we want to checking these states, we just need to console.log the variable or check the type of variable. type of "undefined" is "undefined" while type of "null" is object. That's it.

The generator function can return multi values (using "yield"). The special thing about the generator function is it can be exited and later re-entered. It's not like the normal function, when we call the function, we can't stop it until it's done with all code written inside the function. The generator function will execute until it encounters the yield keyword. To declare a generator function, we using function* syntax.

Example:

jsx
function* generateSequence(){
	yeild 1;
	yeild 2;
	yeild 3;
}
function* generateSequence(){
	yeild 1;
	yeild 2;
	yeild 3;
}

When generator function is called, it doesn't run its code, instead it returns a special object, called generator object

  • The generator object form's is
jsx
{
	value: any - the returned value from the current yeild],
	done: boolean (true | false)  - indicate that there is no yeild left or not
}
{
	value: any - the returned value from the current yeild],
	done: boolean (true | false)  - indicate that there is no yeild left or not
}

The main method of generator is next(). When it's called, it runs the execution until meet the next yeild

For example:

jsx
function* generateSequence() {
  yield 1;
  yield 2;
  return 3;
}

let generator = generateSequence();
*let one = generator.next();*
alert(JSON.stringify(one)); // {value: 1, done: false}
function* generateSequence() {
  yield 1;
  yield 2;
  return 3;
}

let generator = generateSequence();
*let one = generator.next();*
alert(JSON.stringify(one)); // {value: 1, done: false}
  • Note: Syntax: function* f(...) or function *f(...) ? Both are correct, but usually the first is preferred. Because the star * symbol denotes that it's a generator function, it describes a kind, not a name.

We can loop over the values of generators by using for..of:

For example:

jsx
function* generateSequence() {
  yield 1;
  yield 2;
  yield 3;
}

let generator = generateSequence();

for (let value of generator) {
  alert(value); // 1, then 2, then 3
}
function* generateSequence() {
  yield 1;
  yield 2;
  yield 3;
}

let generator = generateSequence();

for (let value of generator) {
  alert(value); // 1, then 2, then 3
}

Note: If we replace the last yield with a return statement, the for..of loop will ignore the last value since done : true,

I'm not familiar with this type of function so I won't write much about it. As far as I know, redux-saga use it. Yeah, and that's it. :v

  • Function returns the same result with the same params
  • Function has no interaction with outer world (call api, mutate data, log,...)

Example:

jsx
const add = (a,b) => a + b; // pure function

const getSearchList = async () => {
	const res = await callsearchapi(query);
	...
} // not a pure
const add = (a,b) => a + b; // pure function

const getSearchList = async () => {
	const res = await callsearchapi(query);
	...
} // not a pure

The key here is, no matter how many times or where we call the add function, it will return the same result for specific arguments. (You pass to the function (2,3), it will always return 5).

But the second function getSearchList, if we call that function with the same query but at different times, maybe we will not get the same result (since the data from the backend could be changed tho).

We all know that the promise was born to solve callback hell. To handle promise, we have 2 ways in mind, the first one is using chained call (or widely known as “then" chain) and the second one using await.

The biggest downside of using then chain is about the scope of variables, because with each block code of chained call, we wrapped up a scope to our variable which couldn't shared between chains.

Let's take a look at an simple example

jsx
const connectToDb = async () => {// some code to connect to your project database }

connectionToDb.then(con => {
// do some stuff with established connection
})
.then(res => {
// do some stuff with the returned value from previous chain
})
.then(res1 => {
// do another suffs with the returned value from previous chain
// But, wai, what if I want to access to con variable from the first chain to close it?
});
const connectToDb = async () => {// some code to connect to your project database }

connectionToDb.then(con => {
// do some stuff with established connection
})
.then(res => {
// do some stuff with the returned value from previous chain
})
.then(res1 => {
// do another suffs with the returned value from previous chain
// But, wai, what if I want to access to con variable from the first chain to close it?
});

Can you see the disadvantage of this way now? Yeah of course we can initialize some global or at higher scope to use it all the way down in chains. But it would lead to hard to understand and maintained code and we want to avoid that.

Okay, it's the end of part 2. If you have any question or suggestion, feel free to comment below. Hope to see you guys again soon. Bye bye.

References:

  • stackoverflow
  • hackernoon
  • medium
Tagged:#Front-end#Interview#JavaScript#Web Development
0