Node.js 16

and Modern JavaScript

JaxNode May 2021

About Me

fek.io/blog

youtube.com/c/polyglotengineer

github.com/davidfekke

@jaxnode @polyglotengine1

Node 16

  • Node 14 LTS
  • Can use Node 16 for R&D
  • Use Node LTS for Production

Node 16 Features

  • Apple ARM64 support
  • New V8
  • New Regex functions
  • Stable Timer Promises API
  • Web Crypto API
  • New Node-API C methods
  • AbortController API
  • atob and btoa support
  • Source Maps v3

Apple Silicon M1

  • Build group has installers for Node 16
  • .pkg installer installs Universal Binary
  • Previous versions could use Rosetta 2
  • NVM installs arch specific

V8 9.0

  • New Regex Indices /d
  • Faster Js-to-Wasm calls
  • Use experimental --turbo-inline-js-wasm-calls

Stable Timer Promises

  • Exists under 'timer/promises'
  • Use this for using timers within Async/Await
import { setTimeout } from 'timers/promises';

async function doSomething() {
  console.log('doSomething started!');
  await setTimeout(2000);
  console.log('Delayed Timers!');
}
doSomething();

Web Crypto API

  • Newer version of the Crypto API
  • All new features under `subtle` interface
  • Many versions of `Crypto` 
  • Standardizing and a single Standard

Web Crypto Example

import { webcrypto } from 'crypto';
const { subtle } = webcrypto;

(async function() {

  const key = await subtle.generateKey({
    name: 'HMAC',
    hash: 'SHA-256',
    length: 256
  }, true, ['sign', 'verify']);

  const digest = await subtle.sign({
    name: 'HMAC'
  }, key, 'I love node.js');

  console.log(digest);
})();

N-API now Node-API

  • The way you extend Node with C/C++
  • napi_add_async_cleanup_hook
  • napi_object_freeze
  • napi_object_seal
  • napi_type_tag_object
  • napi_check_object_type_tag
  • napi_type_tag

AbortController

  • Actually part of Node 15
  • Use { once: true } option in Event Listeners
const abortC = new AbortController();
abortC.signal.addEventListener('abort', () => {
    console.log('Just cancelled')
}, { once: true });
abortC.abort();
console.log(abortC.signal.aborted);

atob and btoa Added

  • atob and btoa added to Node for JS compatibility
  • Used for converting Base64
  • DO NOT USE THESE NEW FUNCTIONS
  • There is a better way
const str = 'Hello JavaScript Developer!';

const strBuf = Buffer.from(str, 'utf8');
console.log(strBuf);
// <Buffer 1d e9 65 a0 96 af 69 27 2b 8a 9b 43 7a f7 a5 a2 97 ab>

const base64Buf = Buffer.from(strBuf, 'base64');
const base64Str = base64Buf.toString('base64');
console.log(base64Str);
// SGVsbG8gSmF2YVNjcmlwdCBEZXZlbG9wZXIh

const bufFromBase64Str = Buffer.from(base64Str, 'base64');

const decodedStr = bufFromBase64Str.toString('utf-8');
console.log(decodedStr);
// Hello JavaScript Developer!
console.log(str === decodedStr);
// true

Other Features

  • npm v7.10.0
  • Source Maps v3
  • process.binding() has been deprecated

Modern JavaScript

JavaScript Has Changed

  • Original version released in 1995
  • Changed name to ECMAScript
  • V3 added Regex
  • V4 was scraped and became ActionScript
  • V5 Added JSON libs, Array functions, 'strict mode' and getters and setters in 2009
  • V6 became ES2015, ES2016
  • ES2017
  • ES2018
  • ES2020 ...

Features since 2015

  • let, const keywords
  • for-of loops
  • arrow functions
  • Promises and async/await
  • import/export syntax
  • ... Spread Operator
  • De-structuring
  • Default Parameters
  • new.target
  • nullish coalescing and optional chaining
num = 5;
console.log(num);
var num; // 5
num = 5;
console.log(num);
let num; // error

var catsname = 'Bebe';

function hoistName() {
    othercatsname = 'Zedadiah';
    console.log(othercatsname); // Zedadiah
    var othercatsname;
}

hoistName(); // Zedadiah
console.log(othercatsname);
// Uncaught ReferenceError: othercatsname is not defined

let and const

  • 'var' will hoist to top of scope
  • 'let' and 'const' will not let you hoist
  • 'const' supposed to be write once, readonly

Object Construction

  • function prototypes
  • Use the 'class' keyword
  • class is controversial
  • factory functions
  • Object Literals
  • Object.create, .assign, .freeze
function Thing(name) {
    this.thingName = name;
    return this;
}

Thing.prototype.getName = function() {
    return this.name;
}

const myThing = new Thing('My Thing');

const currentName = myThing.getName();
// returns 'My Thing';

Prototype

class Thing {
    construtor(name) {
        this.thingName = name;
    }

    getName() {
        return this.thingName;
    }
}

const myThing = new Thing('My Thing');
// returns 'My Thing';

Class

function createThing(name) {
    function getName() {
        return name;
    }

    return Object.freeze({
        name,
        getName
    });
}

const myThing = createThing('My Thing');
// returns 'My Thing';

Crockford Style

// Sample from Wikipedia https://en.wikipedia.org/wiki/ECMAScript
let object = {a: 1, b: 2}

let objectClone = Object.assign({}, object) // before ES9
let objectClone = {...object} // ES9 syntax

let otherObject = {c: 3, ...object}
console.log(otherObject) // -> {c: 3, a: 1, b: 2}
// https://fek.io/blog/8-1-2-ways-to-loop-through-an-array-in-java-script/

const arr = ['First', 2, 3.3, 'Foo', 'Bar'];

//for-in
for (let index in arr) {
  console.log(arr[index]);
}

//for-of
for (let item of arr) {
  console.log(item);
}

For-of

Arrow functions


const prn2 = txt => console.log(txt);

const prn3 = (txt) => {
	console.log(txt);
};

prn1('Hello JaxNode1!'); // Hello JaxNode1!
prn2('Hello JaxNode2!'); // Hello JaxNode2!
prn3('Hello JaxNode3!'); // Hello JaxNode3!

function prn1(txt) {
	console.log(txt);
}

Promises async/await

  • Promises and async and await are interchangeable
  • Error-first functions can easily be converted into promises and used with 'async' and 'await'

var fs = require('fs'),
	rootPath = 'C:\\Users\\dfekke\\Documents\\',
	ext = 'tiff',
	newExt = 'TIF';

fs.readdir(rootPath, 
  function(err, files) {
	if (err) {
	  console.error("unable to read directory");
	} else {
	  var re = new RegExp("^.*\\." + ext + "$");
	  for (var f of files) {
		if (re.test(f)) {
			console.log(f);
			var oldPath = rootPath + f;
			var newPath = rootPath 
              + f.substring(0, f.length - ext.length) 
              + newExt;
			console.log(newPath);
			fs.rename(oldPath, 
					  newPath, 
					  function (err) {
						console.error("unable to rename file");
					});	
				}
			}	
		}
});

Error first callbacks

Convert to Promise

  • Some older APIs do not have Promise support
  • Util.Promisify will convert error-first callbacks
  • let fsP = util.promisfy(fs.readdir);
  • let folders = await fsP(path);
  • import { readdir } from 'fs/promises';
  • let files = await readdir(path);
import * as pkg from 'node-fetch';
const fetch = pkg.default;

const getsomedata = async () => {
    const resp = await fetch('https://reqres.in/api/users?page=2');
    const jsondata = await resp.json();
    const names = jsondata.data.map(item => `${item.first_name} ${item.last_name}`);
    return names;
};

const results = await getsomedata();
console.log(results);

Fetch

import axios from 'axios';

const getsomedata = async () => {
    const resp = await axios.get('https://reqres.in/api/users?page=1');
    const respdata = resp.data;
    const names = respdata.data.map(item => `${item.first_name} ${item.last_name}`);
    return names;
};

const results = await getsomedata();
console.log(results);

Axios

import/export syntax

  • Replaces require()
  • Can use .mjs and .cjs extensions
  • set the 'type' in package.json file to either 'commonjs' or 'module'
  • If you set it to 'module' you module will use 'import' and 'export'
  • Also possible to use `require` and `import` in same file.
const fsPromise = require('fs/promises');
const readdir = fsPromise.readdir;

// Can do the same thing on one line with import syntax
import { readdir } from 'fs/promises';

// Can use both at the same time
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const talib = require('talib');
console.log("TALib Version: " + talib.version);
// dataservice.js
import axios from 'axios';

async function getVehicles() {
    const results = await axios.get('https://swapi.dev/api/vehicles/');
    return results.data.results;
}

async function getPlanets() {
    const results = await axios.get('https://swapi.dev/api/planets/');
    return results.data.results;
}

async function getStarships() {
    const results = await axios.get('https://swapi.dev/api/starships/');
    return results.data.results;
}

export { getVehicles, getPlanets, getStarships };

Export

const arr1 = [2, 5, 7, 9];
const arr2 = [1, 3, ...arr1, 8];

console.log(arr2); // [ 1, 3, 2, 5, 7, 9, 8 ]

const [first, second, third] = arr2;
console.log(second); // 3

const obj1 = { f: first, s: second, t: third };
const { f, s, t } = obj1;
console.log(t); // 2

...Spread

function doSomething(greet, time = 3000) {
    setTimeout(() => {
        console.log(`Hello ${greet}`);
    }, time);
}

doSomething('Jaxnode!');
doSomething('Orlando!', 1000);
// 'Hello Orlando!' in 1 second
// 'Hello Jaxnode!' in 3 seconds

Default parameters

function Foo() {
  if (!new.target) 
    throw 'Foo() must be called with new';
  console.log('Foo instantiated with new');
}

new Foo(); // logs "Foo instantiated with new"
Foo(); // throws "Foo() must be called with new"

new.target

undefined ?? "string" // -> "string"
null ?? "string" // "string"
false ?? "string" // -> false
NaN ?? "string" // -> NaN

Nullish coalescing ??

const person = {
	address: {
    	streetnumber: 9080,
    	streetname: 'Simpson St.',
        city: 'Burlington',
        state: 'VT'
    }
};

const zipcode = person?.address?.zipcode;
// Sets to empty value instead of throwing error

Optionals ?.

TypeScript

  • From Anders Hejlsberg, man behind Turbo Pascal, C# 
  • Super set of JavaScript
  • Requires Transpiler like TSC or Deno
  • Allows Data Types for JavaScript
  • Designed for large projects
  • Used with Angular

References

Questions