Node.js Architecture
node someFile.js
will start the event loop process in synchronous-land- will end process after it knows there is nothing else to do
- when asynchronous is present, the event loop stays active until the asynchronous event is finished.
- will often return something with a callback
- if there is no callback, the loop will not stay active waiting for a response
- follows completion order not dispatch order
setInterval()
is asynchronous and will continue to run forever unless the interval is cleared- asynchronous server:
http.createServer((req, res) => {
console.log('got web request');
res.end('yo');
}).listen(3000);
- other architectures use multiple threads
- issues with accessing the same spot of memory
- thread must wait until asynchronous activity is done
Event emitter
const fs = require('fs');
const readStream = fs.createReadStream('./test/hipster-ipsum', {
encoding: 'utf8',
highWaterMark: 256 //bytes
});
const writeStream = fs.createWriteStream('copy.txt');
readStream.on('data', chunk => {
console.log('chunk', chunk.length, data.slice(0,15))
writeStream.write(chunk);
});
// can control data in chunks, read the file through a chunk at a time
readStream.on('close', data => {
console.log('all done');
writeStream.end();
});
Testing Mocha Async
- bring in
promisify
const promisify = require('util');
const unlink = promisify(require('fs').unlink);
beforeEach(() => {
return unlink(actualFileName)
.catch(err => {
if(err.code !== 'ENOENT') throw err;
});
});
- ASCII codes align with byte and hex values
- what files read and convert
Binary
- two states
0
or1
- placement matters
- places
- farthest right is 20, each place increases by 21
- 3 =
11
, 4 =100
- each place is called a bit
- a byte is 2 bits
- any 4 bits has a corresponding hexadecimal code
- 8-bit integer maximum is 255
Hex
- base 16
- once unit is beyond 9, then begin to count in alphabet
- 10 = a, 11 = b, 15 = e, etc.
code
- to write code in binary / hex start
0b
for binary and0x
for hex
Endianness
- byte order
- direction of storage of data
- Little-endian (LE) stores data at least significant figure
- Big-endian (BE) stores data at mostsignificant figure
- there is no endianness for an 8-bit integer as it is one byte
Node Buffer class
- access to
buf.read
utilities- reads based on LE or BE, 8 / 16 / 32 bit files, etc, signed or unsigned (Int / UInt)
- Signed vs unsigned
- signed keep track of negative integers
const fs = require('fs');
const buffer = fs.readFileSync('EDIT.md');
const charB = 'B'.charCodeAt(0);
const charb = 'b'.charCodeAt(0);
const charF = 'F'.charCodeAt(0);
const charf = 'f'.charCodeAt(0);
for(let i = 0; i < buffer.length; i++) {
const number = buffer.readInt8(i);
if(number === charB) buffer.writeInt8(charF, i)
else if(number === charb) buffer.writeInt8(charf, i);
} //converts all b's to f's
const number = buffer.readUInt8(11);
console.log(number) //66
console.log(String.fromCharCode(number)) //b
buffer.writeInt8(70,11) //replacing b with f at the offset 11 (where the b was)
//must write changes made to file when working with buffer
fs.writeFileSync('EDIT.md', buffer)
//unicode characters require 3 bytes
// unsure how to read it with buffer
random notes
command + d
to select similar kindscommand + k + d
to skip selection onto next
- node debugger
- change in settings:
"program": ${file}
- debugger will launch from current file
- step over: go to next line
- step into / out: go into / out of function
- change in settings:
- node gets dunderdirname
__dirname
- where am I and what is my file name?
ES6 review
Template strings / literals
- cannot use control flow within interpolator, can still use ternary as that is an expression
- can multiline without carriage returns
- escape backticks with backslashes
- if you tab within multiline, will show up
destructuring
- can pull properties / methods off of objects if the variable name is the same as the one you want to call it
- can use in functions to pull off desired properties
- can alias
const { person: name, age } = this.person
- requires parenthesis in arrow function to disambiguate
({ note }) => {}
Curly brace useage
- blocks
- object literals
- template literal interpolator
- destructuring
- interpolator in JSX
variable storage
assigned by value
- primitive values
- Number, String, Boolean, undefined, null Symbol
- immutable
assigned by reference
- compound values
- Object, Array
- mutable
- places the contents in a heap and only a reference is stored
- copies of compound objects need to be more creative
- .slice(); for arrays
- {...spread} for objects
variable table
- stores the name of the variable, data type, value
- const declaration will not allow rewriting of data in variable table
- references only point at contained values, NOT other variables or references
- typeof value assigned to variable decides whether the value is stored by value or by reference
- function variable parameters will create a new variable pointer for arguments
var magneto = [8,4,8]; (function(x) { //IIFE x.push(99); console.log(x); //[8,4,8,99] x = [1,4,1]; //reassign variable (create new reference) x.push(88); console.log(x); //[1,4,1,88] })(magneto); console.log(magneto); //[8,4,8,99]
Closures
- point to the variable table, not the value
- functions only remember variables that were defined by a parent
Importing / exporting in node
exposure:
module.exports = {};
exports = module.exports; //do not use as it is easy to overwrite
modules.exports.desiredFunction = () => {};
// preferred way
import:
const imported = require('../labs/art-gallery');
- files are cached when initially required so they are more quickly connected from then on
- files become something like a magic function representing the file
Export patterns
- combine exports together at the end of a file:
module.exports = {
add,
subtract,
multiply
}
- a helper function that modifies functions with different configurations
module.exports = function() {
return function(name) {
function[name]
}
}
- a revealer
module.exports = function(config) {
return //reveal stuff
}
- a class
Classes
extends
- will become an extension of another class
- usually will need to call
super()
to obtain the stuff inside the parent classes constructor - tight coupling, not recommended composition over inheritance
- compose each piece what it needs
- a
has
relationship opposed to ais a
Random Notes:
- add '''js''', '''html''', '''sh''' to markdown code to better highlight things in readME.md
Node.js
-
the environment gives access to
process
.argv
exposes what was typed on the command line- Will split into individual parts
console.log('Hello ${process.argv[2]}'); //command line node greet.js world // Hello world
step by step
// 1. extract argv from cli const name = process.argv[2]; //2. format a greeting to supplied name const greeting = 'Hello ${name}'; //3. log a result to the console console.log(greeting);
- single responsibility principle, easier to change and maintain code
- from a server standpoint, only 2 is relevant
-
If you run with no arguments, you will get a console command line
- command + c to escape
npm
npm init
- creates a package.json
npx
- tests out something installed locally
testing in general
- Unit test : test logic in classes by programmers to show code level correctness. They should be fast and not depend on other parts of the system that you don't intend to test
- Functional acceptance test : test use case scenario's on a limited (specially created) data set done by the test department to show that every specified scenario works as specified.
- User acceptance test : test use case scenario's on production like data done by representatives of the users to make them formally accept the application
- Integration test : Test communication paths between different parts of the module done by the test department or by developers to show that all modules work correctly together.
Mocha vs jest
- Mocha is older and more flexible. Does not come with assertion library or mocking framework. More widely supported. Difficult snapshot testing. Lots of set up.
- Jest is younger and comes as a full package. Easy snapshot testing. Not widely supported
- use with Enzyme to snapshot test react components
unit testing
npm install mocha -D
- a dev dependency
- create a folder with the name
test
within the project root- any name with the ending
.test.js
- any name with the ending
- mocha is a test runner, will need an assertion library to test if things came out as expected
const assert = require('assert');
assert.equal(greeting, 'Hello world');
- do the simplest thing to get your tests to pass
- must
module.export = nameOfFunction
; - in
package.json
underscripts
- "test": "mocha", "start": "npm run test -- -w"
describe()
is a testing suite organizational tool
example:
//cli.test.js
const assert = require('assert');
const getArgs = require('../lib/get-args');
describe('greet cli', () => {
it('extracts name from args', () => {
const name = getArgs(['node, 'greet-cli.js', 'world']);
assert.equal(name, 'world');
})
})
//get-args.js
function getArgs(args) {
return args[2];
}
//greet-cli.js
const getArgs = require('../lib/get-args');
const greet = require('../lib/greet');
const name = getArgs(process.argv);
const greeting = greet(name);
console.log(greeting);
integration testing
- when individual software modules are combined and tested as a group (end-to-end)
execSync
tells something to run, like from the terminal
const childProcess = require('child_process').execSync;
it('works end-to-end', () => {
execSync('node ./bin/greet-cli.js world');
assert.equal(result.toString(), 'Hello World\n');
})
demo:
in calc.test.js:
const assert = require('assert');
//can create a function that will preform the test if need many of the same kind
// Don't do unless there is good reason, often more difficult
describe('calculator', () => {
function calcTest(name, firstAddend, secondAddend, expected) {
it(name, () => {
const sum = calc.add(firstAddend, secondAddend);
assert.equal(sum, expected);
});
}
calcTest('add one and two', 1, 2, 3);
calcTest('multiply', 2,2,4);
});
Functional patterns
- when a function is defined, it is still an object until the magic invocation parenthesis are typed after the function name.
function silly() {}; silly //will only refer the the written function silly(); //will invoke!
- passing functions
function square(x) { return x * x; } //wrap in callback number.map( n => square(n)); //pass directly as return value to hand to map number.map(square); // (won't work), likely never need number.map(square());
Array method demo
- Array.prototype.some():
const some = (array, test) => {
for(let i = 0; i < array.length; i++) {
if(test(array[i])) return true;
}
return false;
}
testing:
describe('some', () => {
const hasEven = some(arr, x => x % === 0);
it('returns false if no elements match', () => {
const arr = [1, 3, 5, 7];
assert.equal(hasEven, false);
})
it('returns true if at least one element matches', () => {
const arr = [1, 3, 4, 7];
assert.equal(hasEven, true);
})
it('some short-circuits when one element is true', () => {
const arr = [2, 3, 5, 6];
const called = [];
some(arr, x => {
called.push(x);
return isEven(x);
});
assert.deepEqual(called, [2])
})
})
- deepEqual assesses if two thing are semantically equal