Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
The Supermarket Notes

The Supermarket Notes

Here are some notes Brian Boyko is using to swot up for the interview with a supermarket client, but I'm saving it to Github Gist so that maybe others can use it. This is mostly stuff I already knew, but it's been a while since I had to get back-to-basics and remember these as concepts, so I did a bit of swotting up, and took these notes.

The Event Loop

Javascript has a call stack; like any LIFO stack, it only pushes and pops; when a function is called, it pushes onto the stack, when the function is returned, it pops that function off of the stack. This is how the program "keeps track" of where it is; when you see an error message, you'll almost always see a printout of the current call stack.

Javascript was designed as a single-threaded language; it does one thing at a time. This makes it great for simplicity but when you have "slow" functions, like a database call or a network request (which may never resolve), then the entire execution will wait for your slow functions. The browser can't render or run any other code - if you want fluid experiences, you can't block the stack.

The practical solution is asynchronous callbacks, passed in (classically) as Higher Order Functions. We give a function a function as a parameter and that function runs some time later inside the function.

But from what we know from the way the call stack is designed, this shouldn't work - not really. Since a blocking function never returns until it's finished executing (including whatever callbacks are provided), asynchronous programming is not possible without something to allow for concurrency.

The reason we can do things concurrently in Javascript is because the browser (and Node*) is more than just the Javascript Runtime. In the browser, various WebAPIs (DOM, XMLHttpRequest, setTimeout, etc.) are effectively "threads you can make calls to." Which means that Javascript is single-threaded, but V8 is not, and allows (through the WebAPI) access to specialised instances of other threads.

In Node, we don't have WebAPIs, we have C++ APIs, but it's mainly the same thing from a day-to-day use case.

When we call a function that takes a callback (like setTimeout), it pops setTimeout(cb) onto the call stack. That callback (cb) is sent to the WebAPIs in a new thread, and the original setTimeout call is now complete, and can be removed from the JS CallStack, while the new thread in WebAPIs/C++APIs continues to run in the background.

When the WebAPIs are ready to execute, they push the callback into a FIFO task queue. The event loop has one little simple job. If the call stack is empty, it takes the first thing in the task queue and pops it onto the call stack, which effectively runs it.

The takeaway

  • Don't tie up the call stack with blocking synchronous operations. Use asynchronous operations.

  • Javascript will execute all synchronous operations before it will start working on asynchronous operations queued in the (Web/C++)APIs.

  • Callbacks will be executed in the order they have been pushed into the task queue, which is not necessarily the order they have been placed in the WebAPIs.

  • The call stack has limited space, so recursion, while useful to solve some problems, might not be the best solution for looping.

  • If you're trying to defer something until the stack is clear, try putting it inside a setTimeout with a timeout of 0. It will not execute until the stack is clear, even if it takes longer than 0 milliseconds for it to execute.

  • To visualize this, there's a tool called "loupe" you can use. http://latentflip.com/loupe/

Design Patterns

Design patterns are advanced object-oriented solutions to commonly occurring software problems.

Of course, as a functional programmer, I consider "object-oriented programming" to be a "commonly occurring software problem" but let's leave that for now.

Most of the design patterns I'm familiar with through practice without really considering that these patterns have particular names. I even perused through the O'Reilly book on Javascript Design Patterns mostly to find out that most of the solutions are for ES5 or earlier, and that 90% of them would never be used (and in fact would not have passed any code review I would have been part of).

For example, this is the singleton pattern:

const UserStore = (() => {
  const data = [];
  const add = (item) => data.push(item);
  const get = (id) => data.find((d) => d.id === id);
  return { add, get };
})();

The problem is that it's not much of a pattern - it's just logically using closures to encapsulate.

Similarly, the Observer pattern:

const Observer = () => {
  let handlers = []; // observers;
  const subscribe = (fn) => handlers.push(fn);
  const unsubscribe = (fn) => {
    handlers = handlers.filter((handler) => handler !== fn);
  }
  const fire = (...args) => {
    handlers.forEach((handler) => handler(...args);
  }
  return {subscribe, unsubscribe, fire}
}

const OnClick = new Observer();

OnClick.subscribe((...args) => console.log(JSON.stringify(args)));

$.clickableButton.onClick(event => OnClick.fire(event))

I mean - if you know how to do closures and currying, 98% of these are going to be super-obvious, right?

Express: Authenticating Routes

To authenticate, typically you'll want to use a JWT; an industry standard.

So: The way authentication works, step by step, is:

  1. The client sends credentials to the server.
  2. The server verifies the credentials, generates a JWT and sends it back as a response.
  3. Subsequent requests from the client put that JWT in the request headers.
  4. Server validates the token, and if valid, provides the requested response.

The JWT doesn't just have the user ID but also the access rights, admin rights. In that way the JWT can be used for authorization (This user has permission to do this thing) as well as authentication (This user is who he/she claims to be.)

In express, this is basically just a bit of middleware that checks the token and does something different if the token is valid or if it is undefined or incorrect.

Express: Server Side Rendering

Server side rendering is primarily used with the Next.JS for React. (Vue has Nuxt.js). But even without the full Next framework, you can also do server side rendering by hydrating instead of rendering.

On the client side, ReactDOM has a .hydrate() method as well as a .render() method - this is to indicate to the DOM renderer that we're rehydrating the app after a server side render.

ReactDOM also has a "react-dom/server" library that can be used inside Node/Express, which will take a component (usually named App) and "ReactDOMServer.renderToString()" will generate an HTML string. Insert that into HTML using standard find-and-replace, and booyah.

Now that's just the basics of it. Next.JS handles most of the heavy lifting with routing, data fetching, redux/contexts etc.

React: Wasted Renders

In Reacts virtualDOM, data that changes in a parent component will re-render each of the children. So if component "root" has children "A, B, C, and D", each of those with components "1, and 2". If the state that you want to change lives in "B2", the virtual dom will render only B2. But if B2's data state derives from "root", a change in state there will trickle down, rerendering A1, A2, B1, B2, C1, C2, D1, D2, just to make a change in B2.

Long story short: Don't put data any higher in the dom tree than it needs to be.

There are cases where you may need to change data at a global level which trickles down to some, but not all, child components. In that case, you'll probably want to use Redux or a React context, which components can subscribe to themselves, independent of their parent. But this too can lead to unnecessary rerenders - if one piece of data of that context changes, every component observing that data will also rerender. (This is not true for Redux, which only listens to the items provided in mapStateToProps... but that gets tricky). As a result, you may find it's just easier to have each provider have it's own seperate concern and then map all the providers together into one UberProvider, subscribing each component to only the data it absolutely needs.

And of course, there's also React.Memo() which will memoize components based on specific data, so if the data doesn't change, it will choose the render from memory, compare it against the existing dom, and not change if it's the same - but that's kind of a hacky solution and comes with it's own overhead. (Sometimes, pure functions are faster than memoizing).

React: Suspense/Lazy

To avoid winding up with a large bundle, you're going to want to code-split, and then lazy load (through the import() function), which Webpack will automatically code-split for you.

Adding onto this, React.lazy() allows you to pass in the dynamic import of a component as a callback, so this will automatically load the bundle containing the component when first rendered.

So, for example:

/* will add to bundle */
// import Component from './LazyComponent';

/* will code split */
const LazyComponent = React.lazy(() => import("./LazyComponent"));

While the browser is grabbing the lazy loading component, the component allows you to have a fallback (usually a loading screen);

const Loading = () => <div>Loading...</div>;
const LazyComponent = React.lazy(() => import('./LazyComponent'));

export default function MyComponent() {
  return (<React.Fragment>
    <Suspense fallback={<Loading/>}>
      <LazyComponent>
    </Suspense>
  </React.Fragment>);
}

If a module fails to load, you can create an ErrorBoundary to handle that, using the componentDidCatch lifecycle method (for which there is not yet a Hooks equivelent).

Performance: Memory Leaks in JS.

Javascript is a garbage-collected language, so you don't have to worry about deallocation like you would in C, or C++, and don't have to keep track of lifetimes and ownership, like in Rust.

However, you can end up with memory leaks if you:

  • Have global variables (or variables that are on the window object). 'use strict' should take care of most of these.

  • make sure to clear setTimeouts and setIntervals, especially if the timer makes references to data no longer required.

  • make sure to remove event listeners when no longer needed.

  • if manipulating the DOM directly (which you shouldn't do), make sure you delete references to DOM elements as you remove those elements from the DOM, unless you EXPLICITLY want to store that in memory for later use.

Chrome has a memory profiling tool you can use to detect these.

Security: SQL Injection

This is simple: Make sure that user input is escaped before being executed.

// bad
connection.query(
  `SELECT * FROM students WHERE name = ${req.body.name} AND graduating_class = ${req.body.graduating_class}`,
  function (error, results) {}
);

// req.body.name = "Robert`); DROP TABLE students;--";
// https://xkcd.com/327/

// sanitized for your protection
connection.query(
  `SELECT * FROM students WHERE name = ? AND graduating_class = ?`,
  [req.body.name, req.body.graduating_class],
  function (error, results) {}
);

Security: CSP

Remember to set the content security policy in Express to prevent Cross Site Request Forgery:

var express = require("express");
var app = express();

app.use(function (req, res, next) {
  res.setHeader(
    "Content-Security-Policy",
    "script-src 'self' https://apis.google.com"
  );
  return next();
});

Most browsers will NOT allow this to occur due to same-origin-policy restrictions built into the browser. This, however, can make localhost testing tricky, so the target website may explicitly open up cross-origin requests using CORS headers:

In other words, DON'T DO THIS:

// NO!
Access-Control-Allow-Origin: *

Access control should be given only to those domains when absolutely necessary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.