JavaScript

Understanding Babel internals

Object.preventExtensions/seal/freeze

All tested with node --use_strict (to prevent silent errors).

const obj = { a: 1 };
Object.preventExtensions(obj);
obj.b = 2; // TypeError: Cannot add property b, object is not extensible ❌
obj.a = -1; // βœ…
delete obj.a; // βœ…
Object.getOwnPropertyDescriptors(obj); // { a: { value: 1, writable: true, enumerable: true, configurable: true } }
const obj = { a: 1 };
Object.seal(obj);
obj.b = 2; // TypeError: Cannot add property b, object is not extensible ❌
obj.a = -1; // βœ…
delete obj.a; // TypeError: Cannot delete property 'a' of #<Object> ❌
Object.getOwnPropertyDescriptors(obj); // { a: { value: 1, writable: true, enumerable: true, configurable: false } }
const obj = { a: 1 };
Object.freeze(obj);
obj.b = 2; // TypeError: Cannot add property b, object is not extensible ❌
obj.a = -1; // TypeError: Cannot assign to read only property 'a' of object '#<Object>' ❌
delete obj.a; // TypeError: Cannot delete property 'a' of #<Object> ❌
Object.getOwnPropertyDescriptors(obj); // { a: { value: 1, writable: false, enumerable: true, configurable: false } }
// You can basically only read:
console.log(obj.a);

Note: Flow tracks these flags on objects: frozen, sealed and exact.

Beyond console.log()

https://medium.com/@mattburgess/beyond-console-log-2400fdf4a9d8

  • console.log/warn/error with %s, %o and %c
  • console.dir
  • console.table
  • console.assert
  • console.count/countReset
  • console.trace
  • console.time/timeEnd
  • console.group/groupCollapsed/groupEnd

Optional chaining gotchas

(function() {
'use strict';
undeclared_var?.b; // ReferenceError: undeclared_var is not defined
arguments?.callee; // TypeError: 'callee' may not be accessed in strict mode
})();

Node.js LTS or not?

Node LTS is primarily aimed at enterprise use where there may be more resistance to frequent updates, extensive procurement procedures and lengthy test and quality requirements.

Generally if you are able to keep up with the latest stable and future Node releases you should do so. These are stable and production ready releases with excellent community support. Unstable and experimental functionality is kept behind build and runtime flags and should not affect your day to day operations.

https://stackoverflow.com/a/34655149/3135248

Does it mutate 😱

Jest: test.concurrent( ... )

TODO: https://github.com/facebook/jest/pull/1688/files

V8 Built-in functions

function foo() {
const x = { bar: 'bar' };
%DebugTrackRetainingPath(x);
return () => { return x; }
}
const closure = foo();
gc();

vvv

πŸ’ƒ universe [master] node --allow-natives-syntax --track-retaining-path --expose-gc src/test.js
#################################################
Retaining path for 0x33a90e9bcb89:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Distance from root 3: 0x33a90e9bcb89 <Object map = 0x33a9d65bbf09>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Distance from root 2: 0x33a90e9bcb51 <FunctionContext[5]>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Distance from root 1: 0x33a90e9bcc09 <JSFunction (sfi = 0x33a99d8d7a89)>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Root: (Isolate)
-------------------------------------------------

Yarn comments in package.json

{
"private": true,
"devDependencies": {
"//": [
"Please note: `react` dependency here is necessary in order to solve hoisting issues",
"with React Native (Expo) and their locked React version. Yarn hoisted wrong version.",
"It can eventually be removed (try Relay and RN-Expo examples to verify it works)."
],
"flow-bin": "^0.95.1",
"react": "^16.8.6"
}
}

https://github.com/yarnpkg/yarn/pull/3829/files (also great example of test.concurrent usage ^^)

Clearing/resetting/restoring Jest mocks

I am never gonna remember this correctly I guess.

  • jest.clearAllMocks() only clears the internal state of the mock
  • jest.resetAllMocks() does the same + it removes any mocked implementations or return values
  • jest.restoreAllMocks() does everything above but it restores the original non-mocked implementation (and works only with jest.spyOn)

https://github.com/facebook/jest/issues/5143

Splitting string

'I πŸ’– U'.split(' '); // βœ…: [ 'I', 'πŸ’–', 'U' ]
'IπŸ’–U'.split(''); // ❌: [ 'I', 'οΏ½', 'οΏ½', 'U' ]

Better alternatives:

[...'IπŸ’–U'];
Array.from('IπŸ’–U');
'IπŸ’–U'.split(/(?=[\s\S])/u);

More info: MDN, stackoverflow.com

Please note - it's still a bit more complicated. Read this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split#Reversing_a_String_using_split()

isObject()

function isObject(value): boolean %checks {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}

Jest implementation:

function isObject(item) {
return item && typeof item === 'object' && !Array.isArray(item);
}

Is this thing a number?

TL;DR - do not use only isNaN for this and write a lot of tests.

StackOverflow implementation (so far βœ…):

function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}

Facebook implementation (❌ fails for many values like null, booleans, Date object, empty strings, ...):

function isNumber(number) {
return !isNaN(number) && isFinite(number);
}

@cookielab implementation (❌ fails for values like 7.2acdgs and it's not multiplatform):

function isNumeric(n) {
return Number.isFinite(Number.parseFloat(n));
}

Please note that isNaN and Number.isNaN differs significantly (isNaN performs a type conversion). The same for isFinite vs Number.isFinite:

In comparison to the global isFinite() function, this method doesn't forcibly convert the parameter to a number. This means only values of the type number, that are also finite, return true.

See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite

Number.isFinite('0'); // false, would've been true with global isFinite('0')
Number.isFinite(null); // false, would've been true with global isFinite(null)

Polyfill (to understand the difference better):

Number.isFinite =
Number.isFinite ||
function(value) {
return typeof value === 'number' && isFinite(value);
};

Dangerous getters

// @flow
type x = {
+address: ?{|
+fullAddress: ?string,
|},
};
class WTF {
_address = {
fullAddress: 'yay',
};
get address() {
const addr = this._address;
this._address = null;
return addr;
}
}
const y = new WTF();
// this is going to explode:
console.warn(y.address?.fullAddress && y.address.fullAddress);
// here is why:
// console.warn(
// y.address,
// y.address,
// );

source: https://github.com/facebook/flow/issues/5479#issuecomment-349749477

Unfortunatelly, Flow cannot uncover this version (which can also explode):

{
y.address && y.address.fullAddress && <Text>{y.address.fullAddress}</Text>;
}

Placement of catch BEFORE and AFTER then

Source: https://stackoverflow.com/questions/42013104/placement-of-catch-before-and-after-then

return p.then(...).catch(...);
// - vs -
return p.catch(...).then(...);

There are differences either when p resolves or rejects, but whether those differences matter or not depends upon what the code inside the .then() or .catch() handlers does.

What happens when p resolves

In the first scheme, when p resolves, the .then() handler is called. If that .then() handler either returns a value or another promise that eventually resolves, then the .catch() handler is skipped. But, if the .then() handler either throws or returns a promise that eventually rejects, then the .catch() handler will execute for both a reject in the original promise p, but also an error that occurs in the .then() handler.

In the second scheme, when p resolves, the .then() handler is called. If that .then() handler either throws or returns a promise that eventually rejects, then the .catch() handler cannot catch that because it is before it in the chain.

So, that's difference #1. If the .catch() handler is AFTER, then it can also catch errors inside the .then() handler.

What happens when p rejects

Now, in the first scheme, if the promise p rejects, then the .then() handler is skipped and the .catch() handler will be called as you would expect. What you do in the .catch() handler determines what is returned as the final result. If you just return a value from the .catch() handler or return a promise that eventually resolves, then the promise chain switches to the resolved state because you "handled" the error and returned normally. If you throw or return a rejected promise in the .catch() handler, then the returned promise stays rejected.

In the second scheme, if the promise p rejects, then the .catch() handler is called. If you return a normal value or a promise that eventually resolves from the .catch() handler (thus "handling" the error), then the promise chain switches to the resolved state and the .then() handler after the .catch() will be called.

So that's difference #2. If the .catch() handler is BEFORE, then it can handle the error and allow the .then() handler to still get called.

Dependency injection

Fun with JavaScript

Sleep sort:

[3, 5, 1, 8, 2, 4, 9, 6, 7].forEach(num => {
setTimeout(() => console.log(num), num);
});

https://twitter.com/JavaScriptDaily/status/856267407106682880