)
}
}
)
(
}
{
)
)
(
)
(
(
{
}
)
(
)
}
)
)
{
(
(
)
)
}
)
(
}

Global Messages Observer

  1. const message = ((events = {}) => ({
  2.   on(name, callback) {
  3.     if (!events[name]) events[name] = [];
  4.     events[name].push(callback);
  5.     return message;
  6.   },
  7.  
  8.   off(name, callback) {
  9.     const listeners = events[name];
  10.     if (listeners) {
  11.       for (let i = 0; i < listeners.length; i++) {
  12.         if (listeners[i] === callback) {
  13.           listeners.splice(i, 1);
  14.           break;
  15.         }
  16.       }
  17.     }
  18.   },
  19.  
  20.   trigger(name, data) {
  21.     const listeners = events[name];
  22.     if (listeners) {
  23.       for (let i = 0; i < listeners.length; i++) {
  24.         if (listeners[i]) listeners[i](data);
  25.       }
  26.     } 
  27.   }
  28. }))();
  29.  
  30. message.on('app:begin', () => { 
  31.   console.log('begin...');
  32. });
  33.  
  34. message.on('app:end', () => { 
  35.   console.log('end...');
  36. });
  37.  
  38. message.trigger('app:begin');
  39. message.trigger('app:end');

This snippet or some variation of it is something I use frequently. It allows for easy global events. This is great for quick prototyping and keeping things decoupled. I’ve used it on small, medium and even large projects and apps.

It can be adjusted to for easy debugging in a myriad ways. Here is an example with slightly different coding convention that tracks if an event wasn’t received by a listener and suggest an alternative event name to try:

  1. // function from https://www.tutorialspoint.com/levenshtein-distance-in-javascript
  2. // I just randomly grabbed this... for fun
  3. const levenshteinDistance = (str1 = '', str2 = '') => {
  4.   const track = Array(str2.length + 1)
  5.     .fill(null)
  6.     .map(() => Array(str1.length + 1).fill(null));
  7.   for (let i = 0; i <= str1.length; i++) {
  8.     track[0][i] = i;
  9.   }
  10.   for (let j = 0; j <= str2.length; j++) {
  11.     track[j][0] = j;
  12.   }
  13.   for (let j = 1; j <= str2.length; j++) {
  14.     for (let i = 1; i <= str1.length; i++) {
  15.       const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1;
  16.       track[j][i] = Math.min(
  17.         track[j][i - 1] + 1, // deletion
  18.         track[j - 1][i] + 1, // insertion
  19.         track[j - 1][i - 1] + indicator // substitution
  20.       );
  21.     }
  22.   }
  23.   return track[str2.length][str1.length];
  24. };
  25.  
  26. const message = (() => {
  27.   const events = {};
  28.   return {
  29.     on(name, callback) {
  30.       if (!events[name]) events[name] = [];
  31.       events[name].push(callback);
  32.       return message;
  33.     },
  34.  
  35.     off(name, callback) {
  36.       const listeners = events[name];
  37.       if (listeners != null) {
  38.         for (let i = 0; i < listeners.length; i++) {
  39.           if (listeners[i] === callback) {
  40.             listeners.splice(i, 1);
  41.             break;
  42.           }
  43.         }
  44.       }
  45.     },
  46.  
  47.     trigger(name, data) {
  48.       // debug only...
  49.       console.log('trigger', name, data);
  50.  
  51.       const listeners = events[name];
  52.       if (listeners != null) {
  53.         for (let i = 0; i < listeners.length; i++) {
  54.           if (listeners[i] != null) listeners[i](data);
  55.         }
  56.       } else {
  57.         // production would never hit this `else`
  58.         // it could even be removed from production in a 
  59.         // number of ways
  60.         console.warn(`no listeners for '${name}'`);
  61.         let matches = [];  
  62.         for (let i in events) {
  63.           matches.push([levenshteinDistance(name, i), name, i]);
  64.         }
  65.         matches.sort((a, b) => a[0] - b[0]);
  66.  
  67.         if (matches[0]) {
  68.           console.warn(`did you mean '${matches[0][2]}' ?`, );
  69.         }
  70.  
  71.         if (matches[1] && matches[1][0] < 4) {
  72.           console.warn(` - or '${matches[1][2]}' ?`);
  73.         }
  74.       }
  75.     },
  76.   };
  77. })();
  78.  
  79. message.on('soda', () => {});
  80. message.on('pop', () => {});
  81.  
  82. message.on('anything', () => {});
  83.  
  84. message.trigger('anythin');
  85.  
  86. // something else that might match
  87. message.on('mything', () => {});
  88.  
  89. message.trigger('anythi ng');

Take a look on the console in the “Try it out…” example.

Anything about the events can easily be tracked and analyzed with little adjustment. You can in theory design a completely decoupled system using this… again, really great for prototyping architectures.

The on and trigger style definitely comes from the days of Backbone, jQuery etc…

Maybe in the future I will make a mock project that uses this to help further explain it

Enum Shorthand

  1. const toEnum = (...values) =>
  2.   Object.freeze(values.reduce((acc, curr) => {
  3.     acc[curr] = curr
  4.     return acc
  5.   }, {}))
  6.  
  7. const Graphics = toEnum(
  8.   'circle', 
  9.   'rect', 
  10.   'triangle'
  11. )
  12.  
  13. console.log(Graphics.circle)

I sure dislike fake enums in js. Something like this at least dries things up – but still, yuk! 😀

C Style Print with Console.log

  1. console.log('print: %s %i %f %o %s', 'two', 1.4, 1.6, { cool: 321 }, [3, 2, 1]);

outputs: print: two 1 1.6 {cool: 321} 3,2,1

Notice how we are casting a float to an integer and an array to a string in the above example. I personally don’t use this much in javascript, but for someone coming from a C/C-like language this should look familiar and could be very useful.

Destructure in Every Function

  1. // read through the comments of this snippet...
  2.  
  3. function dist1(x1, y1, x2, y2) {
  4.   const dx = x1 - x2
  5.   const dy = y1 - y2
  6.   return Math.sqrt(dx**2 + dy**2)
  7. }
  8.  
  9. function dist2({x1, y1, x2, y2}) {
  10.   const dx = x1 - x2
  11.   const dy = y1 - y2
  12.   return Math.sqrt(dx**2 + dy**2)
  13. }
  14.  
  15. // What's the difference here... well
  16. dist1(50, 50, 100, 100)
  17.  
  18. // vs
  19.  
  20. dist2({ x1: 50, y1: 50, x2: 100, y2: 100 })
  21.  
  22. // so what?
  23.  
  24. // With `dist2` the order of the arguments doesn't matter
  25. // and the arguments are named now as a result of being keys
  26. // in an object
  27.  
  28. // How many times have you changed a core function or method as you're
  29. // working on a project?
  30.  
  31. // Let's see another example:
  32.  
  33. // circle(100, 200, 300, 'red', 'blue', 0, 0)
  34.  
  35. // Can you guess what those arguments are? It's not really a big deal
  36. // and editors help with this, typescript helps with this... but what about:
  37.  
  38. circle({ 
  39.   x: 10, 
  40.   y: 110, 
  41.   radius: 120, 
  42.   fill: 'red', 
  43.   stroke: 'blue', 
  44.   velocityX: 0, 
  45.   velocitY: 0
  46. })
  47.  
  48. // how about...
  49. circle({ radius: 50, fill: 'blue' })
  50.  
  51. // or...
  52. circle({ stroke: 'green', x: 40, velocityX: 1 })
  53.  
  54. // etc...
  55. circle({ 
  56.   radius: 50,
  57.   stroke: 'black', x: 200, 
  58.   fill: 'teal',
  59.   velocityY: 1, velocityX: -1 })
  60.  
  61. // In combination with default arguments we end up with a very easy pattern for functions/methods
  62. // with a complex argument signature. gsap (aka TweenLite/TweenMax) has used this pattern for many
  63. // years. I've seen similar things in many languages...
  64.  
  65. // How does the circle function look?
  66.  
  67. function circle({ 
  68.   x = 0, y = 0, 
  69.   radius = 30, 
  70.   fill = 'black', 
  71.   stroke = 'transparent', 
  72.   velocityX = 0, velocityY = 0}) {
  73.  
  74.   const diam = radius * 2;
  75.  
  76.   const circle = document.body.appendChild(
  77.     Object.assign(
  78.       document.createElement('div'), 
  79.       { style: `
  80.         position: absolute;
  81.         left: ${x}px;
  82.         top: ${y}px;
  83.         width: ${diam}px;
  84.         height: ${diam}px;
  85.         background: ${fill};
  86.         border: 3px solid ${stroke};
  87.         border-radius: 100%;
  88.       `
  89.       }
  90.     )
  91.   )
  92.   if (velocityX != 0 || velocityY != 0) {
  93.     setInterval(() => {
  94.       x += velocityX
  95.       y += velocityY
  96.       circle.style.left = `${x}px`
  97.       circle.style.top = `${y}px`
  98.     }, 16)
  99.   }
  100.   return circle
  101. }
  102.  
  103.  
  104. // here is a golfed distance function - for no reason
  105. d=(a,b,c,d,e=a-c,f=b-d)=>Math.sqrt(e*e+f*f)
  106. console.log(
  107.   dist1(0, 0, 140, 140) ===
  108.   d(0, 0, 140, 140)
  109. )

Map vs WeakMap

  1. const map = new Map()
  2.  
  3. map.set(1, 'one')
  4.  
  5. console.log(map.get(1))
  6.  
  7. const weakMap = new WeakMap()
  8.  
  9. // this will fail with an error:
  10. // weakMap.set(1, 'one')
  11.  
  12. // console.log(weakMap.get(1))

Noticed this gotcha the other day. For some reason WeakMap can’t have integers as keys. After years of using WeakMap I guess I’ve only ever used objects as keys and assumed it could just have keys of any type. Using a Map instead solves the problem, but you’ll need to be careful to manager your references to properly clear the Map. Anyway, just one of those things… Maybe I’ll write more about it when I have some time to dig deeper.

snippet.zone ~ 2021-24 /// {s/z}