Skip to content

Instantly share code, notes, and snippets.

@kepta

kepta/blog.md Secret

Created April 11, 2018 14:33
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kepta/714c04fc4a113e471b97929d6f492dae to your computer and use it in GitHub Desktop.
Save kepta/714c04fc4a113e471b97929d6f492dae to your computer and use it in GitHub Desktop.

Javascript Underdogs: Part 1 - The WeakMap

wmap

Hello 2018! It has been 3 years since we first saw Javascript 2015 (aka ES6). During this time most of us have focused on the cosmetic changes like Arrow => Functions or the fancy destructing operator … .

gif1

Everyone needs something exciting like the upcoming fancy |> pipe operator. Who cares if ES6 also added things like WeakMap, WeakSet, Iterables, Map or Set. Even looking at this thing called WeakMap, feels so depressing 😞

Keeping the sarcasm aside, let us talk about WeakMaps :dancer

Why you would need something Weak

I have to agree the name WeakMap is definitely a misnomer. If it were me I would have named it SuperMap. Before we get into definitions, let us actually take a moment and understand why we need WeakMap's in our apps.

Imagine it’s 1990 🏑 and you create an app of all the countries 🎏 present at that time.

https://gist.github.com/8e578f3dbbbde77149b31c8fe130175b

A user can click any country and get detailed information which also includes the area of the country. Below is a hypothetical area calculation function.

https://gist.github.com/5757727f74c6cc6f46ff336513d57a44

Caching the Area

Every time a user clicks a country you calculate the area. But we have a problem! If a user clicks a country multiple times you have to repeat this enormous asynchronous calculation, which is something we should totally avoid. There are generally two ways to solve this kind of problem.

  1. Debounce the function
  2. Cache the function

Debouncing is a peaceful way to calm down multiple aggressive invocations in a short interval of time. (Imagine an impatient user clicking the refresh button multiple times). Debounce allows us to only take the last invocation and discard the rest.

Since countries don't change area that often, we can simply cache the result of calcArea.

We can use both caching and debouncing to make our application performant. Below is a generic caching function which we will use to cache calcArea.

https://gist.github.com/1bfbd06514135dccca1ec7531c95b652

Great! We made some serious performance improvements.

But we have another problem, USSR just broke into 15 new countries. This would mean we remove USSR and add the newly formed countries to our countries array.

https://gist.github.com/4d4e108e7a577fa7d0f6c7927ac244af

Removing USSR just from the array doesn't help, as our cache still contains USSR and the calculated area. A naive solution would be to monkey patch our cachify function to remove USSR, but if the world continues to break into smaller countries we have got ourselves a memory leak.

We need a smart way to clean up our cache which scales well. There are multiple ways fellow developers would approach this problem:

  1. Maintain a precomputed area array and keep it in sync with countries.
  2. Figure out some smart cache eviction like LRU, time-based, etc.

Precomputing the area for every country seems to be a waste of computation, as most of the users won't ever be seeing every country.

We can use a smart caching strategy like Least Recently Used caching, this caching automatically removes the entry which is least recently used. But we aren't running out of memory with 160+ countries and LRU doesn't seem all that magical and seamless.

What about WeakMap?

wmap

WeakMap is the missing jigsaw piece for our caching problem. It automatically removes* any unused references from it.

"The WeakMap object is a collection of key/value pairs in which the keys are weakly referenced. The keys must be objects and the values can be arbitrary values." - MDN

I like to say WeakMap is nothing but a regular Map with dementia. It is a very forgiving data structure, it will forget things which no longer matter. (We should be like that too :P)

We can simply replace the Map with WeakMap in our caching function.

https://gist.github.com/d29efe26b9468a0f7620c2bc8dde781c

Now let USSR break into the 15 countries. We just need to take care of removing all references pointing to the USSR obj in our app and our cachedCalcArea function will automatically forget the USSR entry in the cache. Hence, avoiding the memory leak!

How does it forget things?

WeakMap works similar to a regular Map but in order to be a forgetful version of Map it imposes these constraints:

  • Primitive data type keys are not allowed (Numbers, String, null, true, etc)
  • You cannot list all the values inside the WeakMap

Let us see a hypothetical example of WeakMap

  • Imagine a WeakMap instance to be a building with thousands of πŸšͺ doors.

    var building = new WeakMap();
  • Each door has a unique key and we own a key πŸ”‘ for our πŸšͺ101. Due to the constraints mentioned above the key can only be an object.

    var key = {
      password: 'πŸ”‘'
    };
  • We can lock/unlock our door with this key.

    building.set(key, 'πŸšͺ101');
    
    building.get(key); // πŸšͺ101
  • Now a thief has seen our key (Its Javascript duh!) and he tries to fabricate a duplicate key.

    var fake_key = {
      password: 'πŸ”‘'
    };
  • Since we live in a Javascript world we clearly know even though they look same, they are not equal.

    fake_key === key // false
  • Our thief didn't read this awesome article and he tries to get into our building using his fake key only to fail :(.

    building.get(fake_key); // undefined

What happens if we lose the key

As long as some variable holds the reference to our original key we are safe. But if there comes a time when no variable in the entire app is holding a reference to our key, we lose the access to our πŸšͺ101.

This is exactly what powers the smart caching of a WeakMap. If we lose the key, the javascript compiler can deduce that there is no way to access the thing associated with the key and it can safely remove it from the memory.

Note: This is the crucial difference between a WeakMap and Map. WeakMap removes <key,value>if you lose the key, but in a Map, you can simply list all the keys to find the lost key.

Coming back to USSR problem, when USSR breaks into the 15 countries and we just need to take care of removing all references to the USSR obj in our app.

https://gist.github.com/0cd1dac3147cf8b4ed4c7d5a0d26ea9a

As you can see after the above steps, there is no way of accessing the USSR object in the current state of app and with this knowledge Javascript garbage collector automatically clears the memory it reserved for the area of USSR. Notice the removing happens behind the scenes and all we did was replace Map with WeakMap. Isn't that powerful?

WeakMap Takeaways

  • Remember not to mutate the key object because in Javascript the object reference stays the same even if you mutate the object.

https://gist.github.com/12b3ab1d8a821cdad5bc2686f386b72c

  • WeakMap can not accept primitive javascript values as keys. You should use Map if you wanna use them as your key.

https://gist.github.com/557098a282b94ac3857e7017886609d1

  • Sometimes it is faster to not cache a function. If your function barely takes a millisecond to execute, you would end up slowing it down by caching.
  • You can use any kind of thing as a value for WeakMap or Map. Yes even promises!
  • The eviction of an unsued key doesn't happen immediately. It depends on the garbage collector's mood. You shouldn't worry about this part though.
  • WeakMap works great for derived state. A lot of times your application has state which can simply be derived from other state. In example below, you can see deriving a value using cached function is much more maintainable and easier to reason with.

https://gist.github.com/a9589542792a9d868381169624d69c22

I really hope this article helped you in understanding WeakMaps. I love using it with libraries like Immutable.js or Redux since they enforce immutability. Even if you don't use these libraries, as long as you don't mutate the object you can reap benefits from WeakMap.

I am planning on writting a Part-2 of Javascript Underdogs, let me know in the comments what Javascript feature you think is amazing but underapreciated.

If you ❀️ this article, please share this article to spread the words.

Reach out to me on Twitter @kushan2020.

@kepta
Copy link
Author

kepta commented Apr 11, 2018

out

@kepta
Copy link
Author

kepta commented Apr 11, 2018

screen shot 2018-04-11 at 11 47 53 am

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment