2020 started out rough for a lot of us. Most of us moved out of the office, and into our couches, kitchens, closets, or otherwise improvised office areas. Whereas you might have lost some precious moments in your car or the gratuitous sneeze-in-your-face on the commute, most of us gained something far more important; Time.
I have tried my best to put this time to use as best I can. Whether practicing inversions on the piano, getting that extra hour of exercise, or scrolling through whatever blogs and articles I could find that seemed like my cup of tea.
I also dedicated some of this time to document things I have learned during the day. Whenever I found something, learned something new or otherwise gained insight or knowledge into an area I deemed relevant to my professional field, I would make a note of it, and when given the opportunity, test it out for myself and detail my findings and sharing them.
This could be anything from finding a new useful code snippet to gaining insight into a whole new concept or paradigm. Today I want to share with you some of my favorites from this list, and I hope you will enjoy and be inspired to take this path for yourself.
I am focusing exclusively on JavaScript for this article. I have learned other things as well, but I think the core audience would not deem my aspirations for lego, piano nor amateur furniture builds relevant. That being said, this is starting to feel like one of those recipe introductions that describes the soups' relationship to Napoleon's great-grandfather. Just give me soup?
Soups, ahoy. Without further ado, here are some excerpts, and my favorite things, that I learned during 2020, in the order that I learned them.
1. Array.sort() behaves differently across browsers
This is more of a mistake that I scratched my head over for a long time earlier this past spring. Consider the following:
When sorting an array, me, and probably a lot of other people, fell into the trap of using this simple callback: sort((x,y) => x < y))
However, when running this on a simple example. We get the following in Chrome and Firefox respectively:
The comparison callback that sort()
accepts must return either 1
, 0
or -1
. Since our callback returns neither, but rather booleans. The different implementations of javascript across these browsers seem to have their own quirks in how they interpret these booleans.
So while Firefox for example seem to accept, or rather interpret, booleans as 1
and -1
respectively, there is no guarantee that other browsers do the same thing, or even that Firefox itself will continue to do so in the future.
Joakim does not accurately read Array documentation. Do not be like Joakim. Read the documentation.
Chrome also used to implement different sorting algorithms depending on the size of the array although this has since been changed to always use a stable sorting algorithm.
2. JSON.stringifys optional replacer argument.
Everyone and their mother who has ever dabbled in web development have, or will, at some point used JSON.stringify
. But this year it dawned on me the second argument to this function: replacer
. Which can be used as a sort of whitelist for key value pairs when parsing JSON.
BUT replacer can also be a function, which might be used to validate, replace or parse (or anything else under the sun. Creativity hat on, gals and guys) the JSON key value pairs.
This comes with a slight hit to performance, as you can see for yourself in these benchmarks, but for the average usecase it should not be a significant difference.
Read more about JSON.stringify()
3. Array.filter() does not work with Promises
I tend to work a lot with array methods. A really common pattern would be: Perform a number of asynchronous operations, iterate over said operations to manipulate the data & filter out unwanted things.
I first ran in to this during the summer, when in my project, we used a function to check the users access levels, when performing certain operations. Let's call it userCan
. By having this function return true or false, it could seamlessly be used as a filter
callback to determine whether the user had relevant permissions for any given set of actions.
Consider actions
a batch of requests the user has made. We need to make sure only the actions the user has permission to do gets executed. Simplified code, but you get the gist:
Short and sweet. But what happens when we introduce asynchronous operations in userCan
?
Suddenly every user action is allowed! The user should not have permission to DROP_DATABASE
. Why is this happening?
Well, Unfortunately Array.filter()
is a synchronous function, and therefore does not support Promises. Due to this, filter()
will not await userCan
to resolve a value, but just accept the Promise as a truthy value.
To get around this, we need to get a bit creative. There are a few ways to work around this, the most common, and the way I would recommend, would be to use .map()
in conjunction with .filter()
.
What we are doing here is using the asynchronous supported .map()
to check the given predicate, and when all Promises has resolved, filter out the falsy values from the resulting array.
You could also do this with .reduce()
, but I find it a bit more convoluted.
Neat, ain't it?
4. Nullish coalescing vs the logical OR operator
A lot of people, myself included, probably feel very familiar writing something like the following, to have a fallback in case baz
is undefined
:
const foo = baz || "fallback"
||
checks for falsy values such as false
, null
and 0
. But there might be cases where you need to treat 0
as an actual value, or otherwise more explicitly handle nullish values.
Nullish coalescing differs from ||
in that it only returns it's right-hand side operand when its left-hand side value is null
or undefined
.
Nullish coalescing was introduced in TypeScript as of version 3.7 and is currently supported in the latest version of all major browsers and Node.js 14
More about Nullish Coalescing here
5. Console.table()
This blew my feeble mind when I ran into it the first time this summer. We're all experienced developers, so we use console.log
as frequently as possible as it is possibly the strongest debugging tool there is! Well, here I am to tell you, that console.table()
exists and how great it is:
Whenever you're working with long arrays of objects, this can be a lifesaver.
More about console.table() here
6. The Promise constructor anti-pattern
The Promise constructor anti-pattern, or the deferred anti-pattern, refers to creating new objects, or in this case new Promises without reason. This is something I was very guilty of when I just started out as a junior developer and began delving into Promises.
It complicates code and prevents rejections and errors from being properly propagated.
I didn't know this was a recognized anti-pattern, and I myself just kind of naturally removed myself from the habit, until I learned more about it this year. It's easy to fall into this kind of way of working when you're new to Promises, and might not know that performing asynchronous operations already returns a Promise, which you in turn can chain.
Rather than explicitly creating a new Promise, it is adviced to just use the original one returned from the async function we are calling in the first place:
Doesn't that look a lot prettier?
Read more about the Promise constructor anti-pattern here
7. Catch awaited errors
Now to something a little more bite sized. We all know you handle resolved and rejected promises by using then
and catch
. But if you want to catch an error using async/await you, to my knowledge, needed to wrap it in a try/catch block, like so:
But to my own amusement and amazement, I recently found that you can in fact use .catch()
on awaited Promises.
Granted, this makes a lot of sense when you think about it, given that async/await is really just syntactic sugar on top of Promises.
That being said, one might present the argument that one should stick to one convention or the other, and while I would usually agree with this sentiment, I just find it really handy in some cases. What do you think?
8. Using optional chaining for function calls.
This might be common knowledge, and as someone who rigorously uses optional chaining, I wonder why it never occurred to me to try this; You can use optional chaining for function calls?
It's beautiful.. And often comes in handy when working with React components for instance, which may or may not have event handlers such as onClick
. No more onClick={onClick && () => onClick(param)}
.
Learn more about optional chaining here
9. Utilizing Web Workers
JavaScript run-times are single-threaded environments, meaning code executions within a program can not be run in parallel, in contrast to for example Java and C#. This means that it's code execution by nature is blocking. We have to wait for one piece of code to finish, before moving on to the next.
Not to be confused with Promises, or Node.js Child Processes, Web Workers are a means to bring this multi-threaded behaviour to JavaScript. They can be utilized for a multitude of things, but probably most commonly used to delegate CPU intensive tasks to other threads, preventing long interruptions and user interface blockers.
Similar to event listeners and Socket.io
, workers communicate with each other using a messaging system and is very simple to get up and running.
For the worker to listen and respond to the messages sent by the main thread, we just need to define the onmessage
handler and we're good to go.
All we need to do now is make sure our main thread has some way to handle the messages sent back by our worker.
While Web Workers can be very useful, they do come with some limitations, such as not having access to the DOM.
Read more about Web Workers here
10. Not depending on dependencies
Last, but not least, I want to give this honorary position in the list to one thing that probably taught me the most during this year.
Earlier last spring, Deno launched version 1.0 with an array of interesting features. Deno is a new, secure JavaScript and TypeScript runtime by Node.js creator Ryan Dahl (ry).
I'm not going to go deep into Deno here, but here's a link if you want to learn more about it.
Me, being an avid Node.js engineer, jumped at the opportunity to discover what it can do. I dedicated a weekend to trying to write a file- & web server that:
Has live reload
Supports client routing
Requires zero configuration..
.. and at the same time being completely dependency free. Meaning no third-party packages can be used. The resulting project is called Denoliver, and I went on to release version 2.0 later in 2020.
I learned an absolute truckload (no swearing here!) from this experiment. There's something highly intriguing having to solve problems most commonly solved with npm install solution
.
I cannot recommend this approach enough. It really was an amazing journey. If you want to check out the project yourself for inspiration, or to contribute, you can find it here.
joakimunge / denoliver
A simple, dependency free static file server for Deno with possibly the worst name ever.
Denoliver is a small, zero config dev & static file server with live reloading written in TypeScript for Deno intended for prototyping and Single Page Applications.
Prerequisites
To run this you need to have Deno 1.0 or later installed.
Key Features
- Dependency free! No third party dependencies.
- Live reload
- Supports client side routing for Single Page Applications.
- Directory lists
- Supports HTTPS
- Allows for programmatic use as a module
- Boilerplating for rapid prototyping.
- Injectable HTTP request interceptors. (TS & JS)
Getting started
Install as a Deno executable.
NOTE: Deno is a secure runtime by default. You need to include the
--allow-net
,--allow-read
and--allow-write
flags to make sure Denoliver can serve your directory.
$ deno install --allow-net --allow-read --allow-write --allow-run https://deno.land/x/denoliver/mod.ts
or if you're not happy with the name:
$ deno install -n whateverNameYouWant --allow-net --allow-read --allow-write --allow-run https://deno.land/x/denoliver/mod.ts
Why do I need the --allow-run
flag?
You don't need it!…
Closing
2020 has been an interesting time for everyone. I am very fortunate to be able to say that I have grown a lot during this time, and I have to give this small process a lot of credit as to why that is.
It might seem like a peripheral thing, but I highly recommend taking a couple of minutes each day to reflect over what knowledge you have gained and jot it down in your medium of choice. Nothing is too big, or too small. By the end of the year, or possibly a lot sooner than that, you will be amazed with the knowledge you have gained.
Hope you enjoyed the little tidbits of my journal today.
What have you learned this year?
Top comments (8)
Why is it so common to write
instead of
I see no reason for this, but maybe there is.
Great write up! Some real gold nuggets here!
Thank you, Daniel! Glad you enjoyed it!
Is JavaScript a hard teacher? Luckily it's not too buggy. Weird behavior of code drives everyone crazy. We have seen enough. Stale network connections, over active garbage collectors, SQL queries switching to bad execution paths, etc.
Learned a lot of new things thanks for this article. Hope you learn more in 2021
That's great! I hope so too!
This is an amazing piece. It’s short yet useful. Keep it up.
Thank you! Definitely will! Glad you liked it.