What I Learned at Work this Week: Friendly IFrames and Debounce

Mike Diaz
7 min readNov 14, 2020
Photo by Jeffrey Czum from Pexels

We’ve got a shorter blog post this week because I’m spending some time this weekend preparing to speak about my job search at Bootcampers Anonymous on Monday night. But the learning never stops and I’ve been meaning to do some research into these two topics after I had trouble understanding some code in the office. Let’s take a look, starting with the iFrame.

What is an iFrame?

An iFrame is an HTML element that can show content from another site. They are commonly used to display things like a Twitter feed, a Google map, or an ad. They’re very relevant in my office because my employer helps clients collect customer email addresses and phone numbers for communication. If you’ve ever seen a “sign up to get 10% off” pop up, there’s a good chance that it’s an iFrame. Rather than show one of those, I’ll show an ad, since that doesn’t put me at risk of accidentally publishing something private or proprietary:

Our Yahoo Sports page is meticulously formatted to include all sorts of links and elements. And right in the middle of it is a meticulously formatted ad. Yahoo doesn’t want the ad to accidentally break their site’s formatting and the ad server doesn’t want Yahoo’s styling to mess up their display. In devtools, we can see that the ad is fully contained in an iframe with its own styling elements. The src element determines which ad will be displayed.

The iFrame is like a window to a different site. Its properties determine how large the window is and what a user sees when they look through it. And Yahoo doesn’t have to code any of it — all they have to do to earn their ad revenue is attach the seller’s script onto their document. The script likely uses analytics to determine what the user would most likely respond to/which advertiser will pay the most for a chance to get that user’s attention (I get Bonobos because I used to work there and my browser history is littered with Bonobos cookies). Then it just changes up the src to show the desired ad.

Friendly vs Unfriendly iFrames

Understanding what an iFrame is or why it’s useful isn’t too complicated, but what confused me in the workplace was why we had different logic in our script based on whether an iFrame was friendly or unfriendly. Luckily, the explanation was much simpler than I had feared. A friendly iFrame shares a domain with the page that hosts it, which would give it the ability to manipulate the html on page beyond the iFrame itself. Perhaps more importantly to advertisers, it means that the iFrame would be able to collect data from the larger page, providing tremendously valuable insight. For these reasons, hosts often prefer to work with unfriendly iFrames, which do not share their domain and are therefore limited in what they can read and write.

This was a big help in understanding the code I was reading, as the “friendly” version wrote elements to the host page’s document itself while the unfriendly version simply loaded everything inside of the iframe. I’m still learning a bit about who determines whether an iFrame will be friendly or not and how this is implemented, but as always, I’m better prepared to have that conversation with this context.

Debounce

Since the iFrame question was pretty straightforward, I decided to look into another topic that confused me: debounce. I came across it this week while reading the definition of a function that determined the size of…an iframe! It used the host window’s size to adjust the iframe’s height so that it wouldn’t interfere with certain elements on the page. The function made sense to me, except for the fact that it was wrapped in another function called debounce. It looked like this:

const largerFunction = debounce(smallerFunction, 250);

This looks a lot like the syntax of a setInterval or setTimeout, which actually isn’t far off from what debounce does! But debounce has a bit of extra logic that makes it smarter than either of those functions.

Debounce’s use case

Debounce is not a predefined function in JavaScript, but it generally written in a similar way to accomplish a similar goal: preventing a callback function from running too frequently. Consider a search bar — it’s a really great experience when we see our search results changing as we type, right?

It might be safe to guess that Google has a giant database with suggested searches, which it queries when I type into that search bar. Querying a database is relatively costly from a computing standpoint and, with a dynamic search like this, the risk is that each keystroke could result in a new query. Rather than scrap our idea for dynamically populating search results because they’re resulting in too many DB queries, we could use debounce to limit the frequency of these queries.

A traditional debounce function takes in two arguments: a callback function and a delay timer amount. The role of a debounce function is to create a version of the callback that is only invoked if debounce has not been re-prompted within the delay timer amount. So rather than running our DB query function each time the value of our search box changed, we could wrap it in a debounce and instead have our function run each time the value changes and remains unchanged for a set period of time.

This post by Trey Huffine does a great job explaining debounce and it includes this example of a classic debounce implementation:

const debounce = (func, wait) => {
let timeout;
return function executedFunction(…args) {
const later = () => {
clearTimeout(timeout);
func(…args);
};

clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};

When writing a debounce function, we can expect that it will:

  1. Establish a timeout variable, but not define it right away. This is necessary because this value must be accessible by both the debounce function and the new function that it is creating. Scope is important here.
  2. Return a function. Debounce is a higher order function, meaning it accepts a callback argument and returns a function that is a version of that callback that will always be altered in the same way.
  3. This new function will define yet another function. When invoked, that function (here called later) will simply clearTimeout and then execute the original callback that was provided as debounce’s first argument.
  4. Run clearTimeout and then setTimeout at the end of the returned function. This is the key to the debounce.

As we can see, the last line of the returned function (here called executedFunction) is the one that finally sets the timeout variable. When we set a variable with the setTimeout function, the timeout count starts, but that variable can also be referenced to stop the count. The clearTimeout invocation above the timeout definition is an example of stopping the timeout. That is to say that if the executedFunction is run twice in quick succession, the first one will setTimeout, but the second one may clear that timeout before it can fully resolve.

I personally find this very confusing, so let’s try to step through it, using debounce as defined above:

const logHello = () => console.log(‘Hello’);
const debounceHello = debounce(logHello, 2000);
// Our expectation is that we can console.log ‘Hello’ for each time debounceHello is invoked without being invoked again in under 2 seconds.debounceHello();// 1. Timeout is declared, but not defined.
// 2. executedFunction is defined and returned. Our function, logHello, doesn’t take any arguments, so executedFunction looks like this:
/*
function executedFunction() {
const later = () => {
clearTimeout(timeout);
console.log(‘Hello’);
};
clearTimeout(timeout);
timeout = setTimeout(later, 2000);
};
*/
// 3. Since executedFunction is returned and invoked, it is…executed.
// 4. We define a function called later, but don’t invoke it. We clearTimeout on a variable that still doesn’t have a value, so nothing happens.
// 5. We set the value of timeout and run setTimeout to execute the later function in 2 seconds.
// 6. 2 seconds later, the function is executed, timeout is cleared, and we log ‘Hello’.

If we were to invoke debounceHello a second time before setTimeout resolved, however, the result of step 4 would be different:

// 4. We define a function called later, but don’t invoke it. We clearTimeout on a variable that was counting down to invoking the later function.
// 5. We set the value of timeout and setTimeout to execute the later function in 2 seconds.

It’s that clearTimeout that prevents this function from executing multiple times within the declared wait period. This helps us manage dynamic search, prevent multiple event firing if someone accidentally clicks a button twice, and, in the case I saw, stops us from rapidly setting and resetting the dimensions of an iframe as a window is being changed. Only after the window’s size has been static for 250 milliseconds will the value be set, saving a ton of potential function invocations.

Finding new ways to learn

I found that I couldn’t understand debounce until I watched a video that I’ll link in my sources. Keep in mind that there isn’t one single way to learn programming. Some people do well reading blogs or books. Others feel best when they’ve coded it themselves. I personally find videos tremendously helpful because I can watch and re-watch the logic working. There’s no one-size solution and there’s no right way to do it. The only mistake you can make is to give up!

Sources

--

--