Polyfill questions are common in Javascript interviews. Some of the most common ones:
etc.
Some time ago I was asked a to create a Promise.five
polyfill, similar to Promise.all
but with subtle differences.
This post will be a write up about how one can tackle a similar question when asked in an interview:
Question: Create a function called minPromises which takes in a single number as an argument. Based on that argument it generates a function that can be used as Promise.five() or Promise.three(). Example: Promise.three() is similar to Promise.all(). It takes in a list of promises and returns when at least three promises resolve.
First Thoughts
It looked like we would have to wait for at least min
promises to resolve. So a counter is definitely required.
We need to create a higher order function that takes in a number and returns another function. That function can be assigned to a new static property of Promise
class like:
Promise.three = {}
Promise.four = {}
Possible doubts that can arise:
What if
Promise.four()
is used with an array of promises of length 3.Should the promise reject with reasons for all the promise failures or a common response for failure?
Should the return array length be same? Some promises will not used in the final result. What should be returned in their indices.
My interviewer cleared them out like this:
- Reject straight away.
- A common Rejection with a sample string will work.
- Empty/null/undefined. Anything. But array order should be maintained just like
Promise.all
.
Initial code:
We start with the inner most function first. We have a min
variable. We can loop through the promises. For every resolved promise we increase success counter. For every rejected promise we increase failure counter.
const min = 3;
function minPromise (promises) {
return new Promise((resolve, reject) => {
if (min > promises.length) return resolve("error");
let counter = 0;
let errCounter = 0;
promises.forEach((promise) => {
promise.then((res) => {
//increase counter
}).catch((res) => {
//increase errCounter
});
});
});
}
Condition checks:
Now, we can put in the code to increase the counter. We should also start forming a result. For that we would need another array
. We would also need the correct index of each promise, so we will have to make use of the second parameter of the forEach
callback.
const min = 3;
function minPromise (promises) {
return new Promise((resolve, reject) => {
if (min > promises.length) return resolve("error");
let counter = 0;
let errCounter = 0;
let resolvedValues = [];
promises.forEach((promise, index) => {
promise.then((res) => {
counter++;
resolvedValues[index] = res;
if (counter === min) {
resolve(resolvedValues);
}
}).catch((res) => {
errCounter++;
});
});
});
}
Reject case:
When does this promise reject?
- When the
min
is invalid, and - when there are enough rejected promises that
counter
can never becomemin
const min = 3;
function minPromise(promises) {
return new Promise((resolve, reject) => {
if (min > promises.length) return reject("error");
let counter = 0;
let errCounter = 0;
let resolvedValues = new Array(promises.length);
promises.forEach((promise, index) => {
promise.then((res) => {
counter++;
resolvedValues[index] = res;
if (counter === min) {
resolve(resolvedValues);
}
}).catch((res) => {
errCounter++;
if (errCounter > promises.length - min) {
reject("error");
}
})
});
});
}
Note: Had to prepend reject with return otherwise Promise flow will continue. Any flow continues till the end until a return statement is reached or an error is thrown. An SO answer, explaining the same.
Wrapping the whole thing up (Pun intended):
The above code looks good. Now we have to wrap it in a function that takes in min
as an input and can generate different versions of this. Using closures this should be easy.
const minPromises = (min) => {
return function(promises) {
return new Promise((resolve, reject) => {
if (min > promises.length) return reject("error");
let counter = 0;
let errCounter = 0;
let resolvedValues = new Array(promises.length);
promises.forEach((promise, index) => {
promise.then((res) => {
counter++;
resolvedValues[index] = res;
if (counter === min) {
resolve(resolvedValues);
}
}).catch((res) => {
errCounter++;
if (errCounter > promises.length - min) {
reject("error");
}
})
});
});
}
}
Now the above can be used like :
Promise.four = minPromises(4);
Promise.six = minPromises(6);
And that's it. We are done.
Afterthought
The question touched up on a lot of concepts like closure, currying, and of course promises. The main idea is to do some action when the individual promises resolve or reject.
Something like this is definitely easier if you have done the sample Promise.all
polyfill. The solution could have used async await
too instead of .then
/.catch
. This is what I could come up with during the interview.
Happy to receive any kind of feedback about this. Do let me know how you would have solve this?
Top comments (0)