- JavaScript Style
- always use curly braces on right
- Always use
break;
inswitch
statement cases - Use a space after a comma, not before
- Use spaces to disambiguate parens
- Wrap Immediately Invoked Function Expressions (IIFE) in parens
- Never rely on auto-semicolon insertion
- Don’t use the
with
statement - Always use
===
, never==
- Don't use multi-line string literals
- Never use assignment inside conditionals
- JavaScript Scope
- Performance
- JavaScript Syntax
- JavaScript Objects
- Classes v Prototypes
- Numbers
NaN
(Not a Number)- Strings
- Arrays
- use
splice()
method (notdelete
keyword) to remove elements from an array - Arrays v Objects
- only use
undefined
, notnull
typeof
prefix operator- Falsy Values
- Objects are passed by Reference
- Identifiers
- Comments
- plus (
+
) operator /
%
is the remainder operator, not the modulo operator- "equal" (
==
) and "not equal" (!=
) operators do type coercion - Boolean
- Statements
- JavaScript Functions
- Closure (aka Lexical Scoping/Static Scope)
- Pseudoclassical OOP - How JavaScript is implemented (worse)
- Prototypal Inheritance (better)
- Functional Inheritance (best)
- Building a better constructor
- Function Challenge 11 (Security)
- Function Challenge 12 (Security)
- Principles of Cryptography
- Principles of Security
- Security is not hats.
- Security is everyone's job.
- Things Change
- Don't nobody do nothing stupid and nobody gets hurt.
- Only adhering to Principles works
- Deterrence is not effective.
- Security must be factored into every decision
- "We'll go back and make it secure later."
- You can't add security, just as you can't add reliability.
- Having survived to this point does not guarantee future survival.
- The Impossible is not Possible
- Don't prohibit what you can't prevent.
- False security is worse than no security.
// bad
block
{
...
}
// good
block {
...
}
why?
// bad (SILENT ERROR)
return // <-- auto semicolon insertion will insert a semicolon here
{
ok: false // useless expression statement (allowed), another semicolon auto-inserted here
};
// compiler thinks above is:
return; // returns `undefined`
{
ok: false;
}
// good
return {
ok: true
};
- fall-through behavior is hard to change/debug
- matches literary style that we are used to
// bad
var i = 0 ,j = 1;
// good
var i = 0, j = 1;
- no space between a
function name
and ( - one space between all other names and (
// bad
foo (bar);
return(a+b);
if(a=== 0) {…}
function foo (b) {…}
function(x) {..}
// good
foo(bar);
return (a + b); // return is not a function!
if (a === 0) {…};
function foo(b) {…} // named function, no space
function (x) // anon function, put a space
Note: webpack/babel/ES6 seem to only like 'dog balls' unless you probably include babel-preset-latest
// bad:
(function () {
...
})() // dog balls
// good
(function () {
...
}());
// bad
x = y // will not insert semicolon
(function () { // will call y as a function, passing another function as its argument
...
}());
// good
x = y;
(function () {
...
}());
// bad
with (0) {
foo = koda;
}
// any of these statements could work depending on `o`, and `o` isn't known at compile time
o.foo = koda;
o.foo - o.koda;
foo = koda;
foo = o.koda;
Why? Because of JavaScript's crazy-bad type coercion.
0 == '' // true
0 == '0' // true
'' == '0' // false
false == 'false' // false
false == '0' // true
"\t\r\n " == 0 // true
Why? Because you'll get a syntax error if you leave a space after the \
,
and many editors won't easily catch this
// ok
var long_line_1 = "This is a \
long line";
//syntax error (space after `\`)
var long_line_1 = "This is a \
long line";
// bad
if (a = b) {...}
// actually does this:
a = b;
if (a) {...}
// doesn't do this:
if (a === b) {...}
Why? variable hoisting:
var
statement get split into two parts:
- declaration gets hoisted to the top of the function, initialized with
undefined
- initialization turns into ordinary assignment
function foo() {
...
var hoistedVar = 0, hoistedVarTwo;
}
// expands to:
function foo() {
var hoistedVar = undefined, // variable declarations are hoisted to the top and set to `undefined`
hoistedVarTwo = undefined;
...
hoistedVar = 0; // variable is assigned at the original point of declaration
}
Why? var
has entire function scope, not block scope
// bad
for (var i = 0 ...) {...} // variable `i` is not scoped to block, but entire function
// better
var i;
for (i = 0 ...) {...} // variable `i` is not scoped to block, but entire function
// best
for (let i = 0 ...) {...} // let respects block scope
- global variables evil, avoid them (why? globals cause coupling, accidental collisions, and security hazards)
- browsers require global variables, but use as few globals as possible
- name globals in upper case:
UPPER_CASE
- try to avoid
new
prefix - Why? Forgetting
new
in front a constructor function causes a constructor to clobber global variables without warning (fixed ES5/strict mode/ES6)
// bad
var a = b = 0;
// doesn't do this:
var a = 0, b = 0; // do this from the start
// instead, does this:
b = 0; // makes b a global variable
var a = b;
why? easier to add statements later, prevents confusion
// bad
if (a) b(); c();
// doesn't do this:
if (a) {
b();
c();
}
// instead, does this:
if (a) {
b();
}
c(); // always calls c
why?
++
causes security errors, inclines one to write hard-to-understand one-liners- avoid pre-increment (
++i
) vs. post-increment confusion (i++
)
// bad
i++;
// good
i += 1;
premature optimization is the root of all evil -Donald Knuth
- don't try to optimize code until you measure its performance
- optimize later, then re-measure performance and if no improvement, scrap the changes
- most "code fiddling" has a negligible impact on performance
- algorithm replacement is vastly more effective than "code fiddling"
- an object is a dynamic collection of properties
- every property has a key
String
that is unique to that object - keys must be strings (automatic type coercion)
- get
object.name
object[expression]
- set
object.name = value;
object[expression]= value;
- delete
delete object.name
delete object[expression]
- make an object that you like
- create new instances that inherit from that object
- customize the new objects
- classification and taxonomy are not necessary (yay)
- good: you don't worry about structure as much as class-based languages early on in a project when things are the most uncertain
- JavaScript implements a Delegation model (aka Differential Inheritance)
- if an object is asked to do something it can't do, it will delegate to another object (up the prototype chain)
- object literals inherit from
Object.prototype
- objects created from a prototype using
Object.create(prototype)
inherit from defined prototype - storing operations always go into bottom most object, reading operations can go all the up the chain to
Object.prototype
- can add material to child object that has no relation to parent
- typically add methods to a parent object, instances will inherit those methods
- avoids inheriting stuff from
Object.prototype
- result of undefined or erroneous operations
NaN
is toxic: any arithmetic operation withNaN
as input will haveNaN
as a resultNaN
is not equal to anything, includingNaN
NaN === NaN // false
NaN !== NaN // true
isNaN(NaN) // true
- No separate character type, characters are strings with length of 1
- Strings are immutable, can make new strings by concatenating multiple strings together
- Similar strings are equal (
===
) - String literals can use single or double quotes
- suggested use:
- use double quotes (
"
) for external strings - use single quotes (
'
) for internal strings and characters
- use double quotes (
- suggested use:
- for
Number
toString
conversion, you may:- use the global
String
function (preferred) - use
Number
'stoString
method
- use the global
str = num.toString(); // ok
str = String(num); // better, works with things without defined `toString` function
- for string to number conversion, you can:
- use the
Number
function - use the
+
prefix operator (preferred) - use the
parseInt(str, [radix])
function (avoid)
why?
Avoid the confusing parseInt
, if you use it, always specify the optional radix
argument:
⚠️ parseInt
stops at the first non-digit characterparseInt(12em) === 12
<!--- `parseInt("08") === 0`-->
<!--- `parseInt("08", 10) === 8`-->
Array
inherits fromObject
- JavaScript arrays are really Objects in which indexes are converted to strings and used as names from retrieving values
- efficient for small arrays, not efficient for large arrays
- advantage: no need to provide a length or type when creating an array (you can do
let a = []
) - every value is "in bounds"
- arrays, unlike objects, have a special
length
property length
always returns 1 larger than the highest integer subscript⚠️ length
is not necessarily the same as the number of elements in the array
- uses
[]
- can contain any number of expressions separated by commas
let list = ['one', 'two', 'three']
- can append items using the array's
length
(:exclamation:)list[list.length] = 'four'
- don't use dot notation with arrays
let n = [4, 8, 15, 16, 23, 42];
n.sort();
// n is now [15, 16, 23, 4, 42, 8] // (!) compares strings
- compare function needs to return a value less than zero, 0, or greater than zero
n.sort( (a, b) => a - b ); // will compare numbers instead of strings, sort ascending
// bad
let arr = ['a', 'b', 'c', 'd'];
delete arr[1];
// arr is ['a', undefined, 'c', 'd'];
// good
let arr = ['a', 'b', 'c', 'd'];
arr.splice(1, 1);
// arr is ['a', 'c', 'd']
- use objects when names are arbitrary strings
- use arrays when names are sequential integers
- in JavaScript,
Object
is an Associative Array
- All values are objects (except
null
andundefined
) null
andundefined
are used to indicate an absence of an objectundefined
is the default value for declared variables and unused parametersundefined
is the value of missing members in objects and arrays
Returns a string identifying the type of a value
typeof
returns 'object'
for arrays and null
use
Array.isArray
orObject.prototype.toString.call
to differentiate regular objects from arrays -MDN
typeof objectName // 'object'
typeof functionName // 'function'
typeof arrayName // 'object' (!)
typeof numberName // 'number'
typeof stringName // 'string'
typeof booleanName // 'boolean'
typeof null // 'object' !)
typeof undefined // 'undefined'
"0"
and "false"
are truthy
// "falsy" values
false // is falsy
null // is falsy
undefined // is falsy
"" // is falsy
0 // is falsy
NaN // is falsy
// everything else is "truthy" (including all objects and all arrays)
"0" // is truthy (!)
"false" // is truthy (!)
-
objects in JavaScript can be passed as arguments to functions and be returned by functions
- objects are passed by reference
- objects are not passed by value
-
the
===
operator compares object references, not valuestrue
only if both operands are the same object
- starts with letter,
_
, or$
- followed by zero or more letters, digits,
_
or$
- convention, start all variables, parameters, members, function names with lower case
- except for constructor functions which start with upper case
- initial
_
should be reserved for implementations $
should be reserved for machines
- use
//
line format - RegEx can interfere with using
/* ... */
-
does both addition and concatenation:exclamation:
-
⚠️ if both operands are numbers, add them- otherwise, convert both to strings and concatenate them:exclamation:
'$' + 3 + 4 = '$34'
- otherwise, convert both to strings and concatenate them:exclamation:
-
use
+
unary operator (with parens) to convert strings to numbers ✅
+"42" // 42
Number("42") // 42
parseInt("42", 10) // 42
+("3") + (+"4") // 7
division of two integers can produce a non-integer result
10 / 3 // 3.3333333333333335
-1 % 8 // -1 (not 7)
- always better to use
===
and!==
which don't do type coercion
// evils of type coercion
0 == '' // true
0 == '0' // true
'' == '0' // false
false == 'false' // false
false == '0' // true
false == undefined // false
false == null // false
null == undefined // true
'\t\r\n ' == 0 // true
1 == [1] // true
true == [1] // true
true == [true] // false
'first' && 'second'
if: 'first' operand is 'truthy',
then: result is 'second' operand
else: result is 'first' operand
'first' || 'second'
if: 'first' operand is 'truthy',
then: result is 'first' operand
else: result is 'second' operand
!operand
if the operand is truthy,
then: `false`
else: `true`
!!
turns a 'boolish' value into a booleans value
//expression
if
switch
while
do
for
break
continue
return
try
throw
- statements can have labels
break
statements can refer to those labels- can use labels to break out of inner statements inside nested statements
loop: for (;;) {
...
if (...) {
break loop;
}
...
}
for
statement used to iterate through the elements of an array
for (i = 0; i < array.length; i += 1) {
// within loop,
// i is the index of current element
// array[i] is the current element's value
}
Object.keys
will return an array of strings of just enumerable own properties of an object
for...in
will iterate through all the names of properties of an object⚠️ for...in
will include names of inherited properties
for (name in object) {
if (object.hasOwnProperty(name)) {
// within the loop,
// name is the key of current property
// object[name] is the current value
}
}
- multi-way branch
- switch value can be a string or a number
- case values can be expressions
- danger: cases fall through to next case unless you use a disruptive statement like
break
orreturn
- can
throw
"anything"
throw new Error(reason);
throw {
name: exceptionName,
message: reason
};
- simple, only one catch block catches everything (no exception types in JavaScript)
- good, since this discourages complicated control paths when using different exceptions
try {
plan_a();
} catch (ignore) {
plan_b();
}
JS functions can do the work of:
- method
- class
- constructor
- module
-
function
expression or literal returns a new function object which can be invoked -
optional name
-
parameters
- wrapped in parens
- zero or more names
- separated by commas
-
body
- wrapped in curly braces
- zero or more statements
-
produces an instance of a function object
-
every time a function expression is evaluated it creates a new instance function object
-
functions objects are first class objects
- may be passed as an argument to a function
- may be returned from a function
- may be assigned to a variable
- may be stored in an object or array
-
function objects inherit from
Function.prototype
- use the
var
statement to declare/initialize variables within function scope - variables declared anywhere within a function with
var
are visible everywhere within the function - remember:
var
gets split into two parts- declaration gets hoisted to the top of the function
- initialization part turns into an ordinary assignment
-
starts with
function
-
mandatory name
-
params/body same as function expression
-
is shorthand for a
var
statement
function foo() {}
// expands to:
var foo = function foo() {};
// which further expands to:
var foo = undefined; // hoisted to function top
foo = function foo() {}; // also hoisted to top of function (should prevent you from declaring a function inside an if statement)
how to tell them apart?
- if the first token in a statement is
function
then it's a function statement (declaration) - otherwise, its a function expression
- with
var
, {blocks} don't have scope, only functions have scope
// don't declare var indexes with same name inside nested loops
// bad
function asssure_positve(matrix, n) {
for (var i = 0; i < n; i += 1) {
var row = matrix[i];
for (var i = 0; i < row.length; i += 1) {
if (row[i] < 0) {
throw new Error('Negative');
}
}
}
}
- declare all variables at the top of the function
- declare functions before you call them
- you don't have to do this two things above, but they are problematic
return expression;
// or
return; // returns `undefined`, expect for constructors which return `this` by default
- is wasteful, new function objects created on iteration
- can be confusing because the new function closes over the loops variables, not over their current values
// bad.
for (var i ...) {
div_id = divs[i].id;
divs[i].onclick = function () {
alert(div_id);
};
}
// above, all divs will display the same id, the last one in the loop
// inner function captures the current value of div_id,
// not the value at the time the function was created. (!)
// good
divs.forEach(function (div) {
div.onclick = function () {
alert(div.id);
};
});
Four ways to call a function:
// 1. Function form
functionObject(arguments)
// 2. Method form
thisObject.methodName(arguments)
thisObject["methodName"] (arguments)
// 3. Constructor form
new FunctionObject(arguments)
// 4. Apply form
functionObject.apply(thisObject, [arguments])
functionObject.call(thisObject, argument...)
- the
( )
suffix contains zero or more comma-separated arguments - the arguments will be bound to parameters
- If a function is called with too many arguments, the extra arguments are ignored
- if a function is called with too few arguments, the missing values will be
undefined
- no implicit type checking on the arguments
neither is recommended
- contains all the arguments from the invocation in an array-like objecti (doesn't inherit all array prototype methods)
arguments.length
is the number of arguments passed- weird interaction with parameters (changing
arguments[0]
changes the first parameter, etc)
function sum() {
var i, n = arguments.length, total = 0;
for (i = 0; i < n; i += 1) {
total += arguments[i];
}
return total;
}
var ten = sum (1, 2, 3, 4); // ten is now 10
this
contains a reference to the object of invocationthis
allows a method to know what object it is concerned withthis
allows a single function object to service many objectsthis
is key to prototypal inheritancethis
is bound at invocation time
allows methods to have a reference to the object of interest
thisObject.methodName(arguments) // `this` is set to `thisObject`
thisObject["methodName"] (arguments) // `this` is set to `thisObject`
functionObject(arguments) // `this` is set to the global object (prevented in
// ES5/Strict mode, `this` is bound to undefined instead)
// inner function will not get access to the outer `this`
// workaround to use outside inner function to get
var that = this; // `this` in scope of inner functions.
var self = this; // use `that` or `self` inside the inner function.
new FunctionValue(arguments) // a new object is created and assigned to `this`.
// `this` will be returned if there is no explicit
// return value in the constructor function.
// used in the pseudoclassical style.
functionObject.apply(thisObject, [arguments]) // allows one to specify value of `this` as
functionObject.call(thisObject, argument...) // `thisObject`.
Function.prototype.call = function (thisObject) { // can also take an array of parameters or a
return this.apply(thisObject, Array // sequence of parameters
.prototype.slic.apply(arguments, [1]));
};
- changes meaning of
this
arguments
var
function
break
continue
return
- a result of the fact that functions can nest, and functions are first class values/objects
- the BIG idea in functional programming
- the context of an inner function includes the scope of the outer function
- an inner function enjoys that context even after the parent functions have returned
// block scope
{
let a;
{
let b;
... a ... // inner block can see it's variables and the variables of the outer block
... b ...
}
... a ... // outer block can only see it's own variables
}
// function scope
function green() {
let a;
function yellow() {
let b;
... a ... // inner function yellow() can see it's variables and the variables of
... b ... // the outer function
}
... a ... // outer function can only see it's own variables (and not yellow()'s)
}
JavaScript introduced closure to the mainstream, was then adopted by python, ruby, C#, c++, and recently Java
// inner yellow() function can survive the life of the outer green() function
// powerful construct
function green() {
let a;
function yellow() {
let b;
... a ... // inner function yellow() can stil see it's variables and the variables of
... b ... // the outer function, even after green() has returned
}
... a ... // outer function can still only see it's own variables (and not yellow()'s)
}
problem: using global variable called names
, if anywhere else in the entire code base (your
code and the libraries you use) contains a global variable called names
, there's a conflict
and your function will fail or a third party function will fail
// global variable `names` is defined
var names = ['zero', 'one', 'two', 'three',
'four', 'five', 'six', 'seven',
'eight', 'nine'];
// function digit_name() uses global variable `names`
var digit_name = function (n) {
return names[n];
};
digit_name(3); // 'three'
no closure, but private array defined again in every function call
// function digit_name() constructs a new private array on every call
var digit_name = function (n) {
var names = ['zero', 'one', 'two', 'three',
'four', 'five', 'six', 'seven',
'eight', 'nine'];
return names[n];
};
digit_name(3); // 'three'
// good: no global variable that could cause conflicts
// bad: everytime you call digit_name(), you construct a new array with ten things in it
// just to take one thing out of it (inefficient)
now immediate function digit_name
returns a function
- every time we nest a function, a new scope is created
- even after
digit_name
returns,names
is still in scope for future calls todigit_name
's returned anonymous function
// closure
var digit_name = (function (n) {
var names = ['zero', 'one', 'two', 'three',
'four', 'five', 'six', 'seven',
'eight', 'nine'];
return function (n) {
return names[n];
};
}());
digit_name(3); // 'three'
// good: no global variable that could cause conflicts, and the
// array `names` is created during the one IIFE call
// `fade()` transitions color of element `id` from yellow to white
function fade(id) {
var dom = document.getElementById(id), // Look up `id` of DOM element.
level = 1; // Initialize `level` to 1.
function step() {
var h = level.toString(16); // Turn `level` into Hex char.
dom.style.backgroundColor = // Step the color one step closer to white.
'#FFFF' + h + h;
if (level < 15) { // Check where we are in the transition.
level += 1; // If not done, increment `level`.
setTimeout(step, 100); // Recursive call to `step()` in 100 ms.
}
}
setTimeout(step, 100); // start the fade transition in 100ms by calling `step`
}
// All recursive calls to step are able to modify `level` because its in scope due to closure.
// At the end of the recursive calls, the garbage collector will clear everything up.
// Will work (no conflict) if fade() is called with multiple DOM elements at
// the same time, since each call to fade() sets a new `dom` variable, new
// `level` variable, and new step() function within their own function scopes.
// They won't interfere with one another because the closure encapsulates and provides seperation.
// make a gizmo using `Gizmo` contructor function, pass in `id` and set to
// newly created property named `id`
function Gizmo(id) {
this.id = id;
}
Gizmo.prototype.toString = function () {
return "gizmo " + this.id;
}
// --> denotes pointer
// ==> denotes delegation link
// `Gizmo function`'s `prototype` property points to the Gizmo's prototype
Gizmo.prototype --> `Gizmo's prototype`;
// Gizmo's prototype's `constructor` property points back to `Gizmo` function
Gizmo.prototype.constructor --> `Gizmo`
// same idea with `Object`
Object.prototype --> `Object's prototype`;
Object.prototype.constructor --> `Object`
// `Gizmo` intance inherits from Gizo's prototype
gizmo = new Gizmo(string);
gizmo ==> `Gizmo's prototype`;
// `Gizmo's prototype` inherits from `Object's prototype`
`Gizmo's prototype` ==> `Object's prototype`
// in other words:
Gizmo.prototype ==> Object.prototype
Gizmo instance
(new Gizmo(string)
)
id |
string |
Gizmo's prototype |
Gizmo
function (constructor function) (all functions created have a prototype
property)
prototype |
Gizmo's prototype |
Gizmo's prototype
constructor |
Gizmo function |
toString |
function |
Object's prototype |
Object
function (constructor function) (constructor of all objects)
prototype |
Object's prototype |
Object's prototype
(all object literals inherit this)
constructor |
Object function |
toString |
function |
Function.prototype.new = function new() {
var self = Object.create(this.prototype), // create a new Object that inherits from Function's `prototype` property
result = this.apply(self, arguments); // call that method, passing in the object and binding it to `this`
return (
typeof result === 'object' && result !== null
)
? result
: self;
};
If we replace the original prototype object, then we can inherit another object's stuff.
// 'extends' in JavaScript's prototypal inheritance model
function Hoozit(id) { // create a `Hoozit` constructor
this.id = id;
}
Hoozit.prototype = new Gizmo(); // `Hoozit` inherits from `Gizmo`
Hoozit.prototype.test = function (id) {
return this.id === id;
};
// --> denotes pointer
// ==> denotes delegation link
// `Gizmo function`'s `prototype` property points to the Gizmo's prototype
Gizmo.prototype --> `Gizmo's prototype`;
// Gizmo's prototype's `constructor` property points back to `Gizmo` function
Gizmo.prototype.constructor --> `Gizmo`
// same idea with `Object`
Object.prototype --> `Object's prototype`;
Object.prototype.constructor --> `Object`
// `Gizmo` instance inherits from Gizmo's prototype
gizmo = new Gizmo(string);
gizmo ==> `Gizmo's prototype`;
// `Gizmo's prototype` inherits from `Object's prototype`
`Gizmo's prototype` ==> `Object's prototype`
// in other words:
Gizmo.prototype ==> Object.prototype
// `Hoozit function`'s `prototype` property points to Gizmo's instance (not its own prototype)
// Gizmo's instance now includes new instance method test (?)
Gizmo.prototype --> `Gizmo's Instance`;
// `Hoozit's instance` inherits from `Gizmo's instance`
hoozit = new Hoozit(string);
hoozit ==> `Gizmo's instance`;
Gizmo's instance
(new Gizmo(string)
)
id |
string |
'test' | function |
Gizmo's prototype |
Gizmo
function (constructor function)
prototype |
Gizmo's prototype |
Gizmo's prototype
constructor |
Gizmo function |
toString |
function |
Object's prototype |
Hoozit's instance
(new Hoozit(string)
)
id |
string |
Gizmo's instance |
Hoozits's prototype
is now unused since Hoozit.prototype
now points to Gizmo's instance
constructor |
Hoozit function |
Hoozit
function (constructor function)
prototype |
Gizmo's instance |
Object
function (constructor function) (constructor of all objects)
prototype |
Object's prototype |
Object's prototype
(all object literals inherit this)
constructor |
Object function |
toString |
function |
As a workaround to improve on the Pseudoclassical style/syntax, some people make a new_constructor
(not recommended)
- can do better than this, by using Functional Inheritance and the Module pattern (described below)
- similar to pseudoclassical inheritance, we save memory by have only one copy of each method per instance
var gizmo = new_constructor(Object, function (id) { // pass in thing you wan to inherit from.
this.id = id; // pass in constructor function.
}, {
toString: function () { // pass in object containing methods you want
return "gizmo" + this.id; // instances to inherit
}
});
var hoozit = new_constructor(gizmo, function (id) {
this.id = id;
}, {
test: function (id) {
return thsi.id === id;
}
});
// looks more like class-based syntax
function new_constructor(initializer, methods, extend) {
var prototype = Object.create(typeof extend === 'function'
? extend.prototype
: extend);
if (methods) {
methods.keys().forEach(function (key) {
prototype[key] = methods[key];
});
}
function constructor() {
var self = Object.create(prototype);
if (typeof initializer === 'function') {
initializer.apply(self, arguments);
}
return self;
}
constructor.prototype = prototype;
prototype.constructor = constructor;
return constructor;
}
- good: avoids using global variables
- okay: causes creation of many instances of functions
make one instance of an object containing two methods that share private variables/functions
- no easy way to get privacy in JavaScript without using function Modules as described here
- Module Pattern uses closure and IIFE that returns two methods that close over the private state held by private variables in the enclosing IIFE
- Module Pattern allows for sharing of private variables, if one method changes the value of one private variable, the other will see the change
var singleton = (function () {
var privateVariable;
function privateFunction(x) {
...privateVariable...
}
return {
firstMethod: function (a, b) {
...privateVariable...
},
secondMethod: function(c) {
...privateFunction()...
}
};
}());
instead of returning an object of methods, could modify a global variable that is the container of everything in your application, and you want to add to it
- works because you are invoking the function immediately
var singleton = (function () {
var privateVariable;
function privateFunction(x) {
...privateVariable...
}
GLOBAL.methodical {
firstMethod: function (a, b) {
...privateVariable...
},
secondMethod: function(c) {
...privateFunction()...
}
};
}());
- if you don't invoke the function immediately, you can hold on to that function and make lots of instances
The Module Pattern is easily transformed into a powerful constructor pattern
Four steps:
- Make an object
- Object literal
new
Object.create
- call another power constructor
- Define some variables and functions
- these become private members
- Augment the object with privileged methods
- privileged methods are publicly available methods of an object which close over the private stuff
- Return the object
- allows to make private state within an Object
- this is a flexible pattern, many different styles of implementations are possible
- the general gist of it is shown below
- makes use of closure to provide private state within the object
// can can with `new` prefix (will be slower) but nothing will go wrong
// `spec` is an object of named parameters
function constructor(spec) {
var self = otherMaker(spec), // 1. Call another constructor to make an object.
member, // 2. Can make as many private member variables as you need (not visible outside object due to function scope)
method = function () { // Create private methods.
// spec, member, method
};
self.method = method; // 3. Priviledge methods (public) assigned to outgoing object.
return self; // 4. Return the object.
}
- functional inheritance will create a new instance of functions for every instance of the object
- uses slightly more memory, but closure allows use to hide properties/behavior from the global scope
function gizmo(id) {
return {
id: id,
toString: function () {
return "gizmo " + this.id;
}
};
}
function hoozit(id) {
var self = gizmo(id);
self.test = function (testid) {
return testid === this.id;
};
return self;
}
- We want privacy, the only way to access
id
is through the methods of the object - JavaScript doesn't provide a way for privacy in the Pseudoclassical Model
- The Functional Model does allow for privacy because of closure
- also allows us to pull out methods into outside variables
no longer specify this.id
in gizmo (since that would make it accessible)
function gizmo(id) {
return {
toString: function () {
return "gizmo " + id;
}
};
}
function hoozit(id) {
var self = gizmo(id);
self.test = function (testid) {
return testid === id;
};
return self;
}
function Hoozit(id) {
this.id = id;
}
Hoozit.prototype = new Gizmo();
Hoozit.prototype.test = function (id) {
return this.id === id;
}
// if we try to pull out the test() method from the object
// (you can copy any object reference and put it in a seperate variable)
// var my_hoozit = new Hoozit("success"),
test = my_hoozit.test;
test("success"); // Boom! Fails
// test() fails here because `this` is not bound to the object but something else
// (`this` will be bound to either `undefined` or the global object)
functional model allows you to pull out functions from the object and call them, they will work exactly the same way (due to closure)
function hoozit(id) {
var self = gizmo(id);
self.test = function (testid) {
return testid === id;
};
return self;
}
// var my_hoozit = hoozit("success"),
test = my_hoozit.test;
test("success"); // true
// works because methods don't have `this` in them
functional pattern: using a function to construct objects
function constructor(init) { // 1. Pass in initialization object.
var self = other_constructor(init), // 2. Call another constructor to inherit what `init` obj does.
member, // 3. Member var(s): store state that internal methods will access.
method = function () { // 4. Member method(s): local functions within this object's scope.
// init, member, method // - Each close over `init` obj, member variables and methods.
}; // - (no need for `this`)
self.method = method; // 5. Anything that needs to be public is assigned to outgoing obj.
return self;
}
function constructor(spec) {
let {member} = spec; // New es6 syntax: same as `let member = spec.member`.
const {other} = other_constructor(spec);
const method = function () {
// spec, member, other, method
};
return Object.freeze({ // `freeze` gives good security and reliablity for this object.
method, // New es6 object literal syntax: same as `method: method,`.
other
});
}
Make an array wrapper object with methods get
, store
and append
, such that an attacker
cannot get access to the private array.
myvector = vector();
myvector.apend(7);
myvector.store(1, 8);
myvector.get(0); // 7
myvector.get(1); // 8
What's could be an attack on the following code? (standard behavior of the language).
Assume that Array.prototype is fixed.
function vector() {
var array = [];
return {
get: function get(i) {
return array[i];
},
store: function store(i, v) {
array[i] = v;
},
append: function append(v) {
array.push(v);
}
};
}
// attack:
// Store a new locally defined push method into the array that uses `this`.
// Then call the method using append() which will dynamically bind `this`
// to the array object at invocation.
var stash;
myvector.store('push'), function () { // store(i, v), i is just a key (doesn't have to be a number)
stash = this;
});
myvector.append(); // append() will invoke locally defined push method instead,
// array.push is a method invocation, so `this` bind to array
// stash is `array`
function vector() {
var array = [];
return {
get: function get(i) {
return array[+i]; // turn a string into a number (will turn 'push' into NaN)
},
store: function store(i, v) {
array[+i] = v;
},
append: function append(v) {
array[array.length] = v; // appending to an array should be an operator
}
};
}
Make a function that makes a publish/subscribe object. It will reliably deliver all publications to all subscribers in the right order. (Subscribers are functions.)
my_pubsub = pubsub();
my_pubsub.subscribe(log);
my_pubsub.publish("It works!"); // log("It works!")
i.e. if an attacker is one of the subscribers, how can he screw it up for all the other subscribers?
Hint: Is possible to prevent publish/subscribe, allows messages to publish out of order.
function pubsub() {
var subscribers = [];
return {
subscribe: function (subscriber) {
subscribers.push(subscriber);
},
publish: function (publication) {
var i, length = subscribers.length;
for (i = 0; i < length; i += 1) {
subscribers[i](publication);
}
}
};
}
// Simple Attack:
my_pubsub.subscribe(); // push undefined onto the subscribers array
A: Could do typeof
on the subscriber
argument for the subscribe
function,
or use try..catch
. See below.
function pubsub() {
var subscribers = [];
return {
subscribe: function (subscriber) {
subscribers.push(subscriber);
},
publish: function (publication) {
var i, length = subscribers.length;
for (i = 0; i < length; i += 1) {
try { // fix 1: catch the error and ignore it
subscribers[i](publication);
} catch (ignore) {}
}
}
};
}
// Attack 2:
my_pubsub.publish = undefined; // possible to tamper with pubusb instance and delete property or
// even replace with more insidious functions
A: Freeze the object
function pubsub() {
var subscribers = [];
return Object.freeze({ // fix 2: freeze the object to prevent tampering
subscribe: function (subscriber) {
subscribers.push(subscriber);
},
publish: function (publication) {
var i, length = subscribers.length;
for (i = 0; i < length; i += 1) {
try { // fix 1: catch the error and ignore it
subscribers[i](publication);
} catch (ignore) {}
}
}
});
}
// Attack 3:
my_pubsub.subscribe(function () { // takes advantage of fact that `subscribers[i](publication);`
this.length = 0; // is a method invocation (!) using bracket notation
// and will `bind` this
});
A: within the loop's try statement could use subscribers[i].call()
or assign
subscribers[i]
to a local variable, or...
function pubsub() {
var subscribers = [];
return Object.freeze({ // Fix 2: freeze the object to prevent tampering
subscribe: function (subscriber) {
subscribers.push(subscriber);
},
publish: function (publication) {
subscribers.forEach(function (s) { // Fix 3: use forEach and pass a
try { // function will call on each element (s) of array.
s(publication); // Fix 1: catch the error and ignore it
} catch (ignore) {}
});
}
});
}
// Attack 4:
my_pubsub.subscribe(limit(function () { // uses limit to limit to just a single call (otherwise will
my_pubsub.publish("Out of order") // call recursively and effectively be a DoS attack)
// and will `bind` this
}, 1));
A: could use a boolean at the top that says we're in subscribe mode, or...
function pubsub() {
var subscribers = [];
return Object.freeze({ // Fix 2: freeze the object to prevent tampering
subscribe: function (subscriber) {
subscribers.push(subscriber);
},
publish: function (publication) {
subscribers.forEach(function (s) { // Fix 3: use forEach and pass a
setTimeout(function () { // function will call on each element (s) of array.
s(publication);
}, 0); // Fix 4: (asyncronous) use `setTimeout` that receives a function
}); // and adds that function to be called into a timer queue
} // (keeps stuff in order, also don't need to use `try...catch`,
}); // if one fails it will fail during that turn and keep going)
}
// Attack 5: (to be solved for another day)
// `setTimeout` returns a number and you can pass that number to
// `clearTimout` to prevent further execution. The number is
// easily guessable by an attacker to prevent messages from being
// delivered by canceling messages still in the queue.
The design of a system should not require secrecy; and compromise of the system should not inconvenience the correspondents.
If there are obvious security vulnerabilities in a system, often the architect's first thought is...
We need to encrypt something
...which turns out doesn't work ❗
Cast of characters
- 👩 Alice
- :squirrel: Eve (eavesdropper)
- 👤 Mallory (Man-in-the-middle)
- :person_with_blond_hair: Bob
- 😈 Satan (malicious attacker)
White hats vs. black hats
Don't leave it to specialists.
It is not unusual for the purpose or use or scope of software to change over its life.
Rarely are the security properties of software systems reexamined in the context of new or evolving missions.
This leads to insecure systems.
And this means you.
Not trix and hax.
You can't punish an invisible attacker.
We have the responsibility to limit the capabilities of 'Satan' so he cannot cause us harm.
Rarely works, is really hard to do.
Insecurity and unreliability must be removed.
If a measure is not effective, it is ineffective.
What you don't prevent, you allow.
Unnecessary expense and confusion of risk.
If you know you're not secure, you'll be cautious.
If you think you're secure and you're not, you'll be reckless.
-
historically and horribly insecure
-
still "fixing it later."
-
HTML5 made it even more vulnerable
-
But it is still the best application delivery system
- if a system has to make a decision about security and doesn't have enough information to make a correct decision, it will ask the user for permission (typically using language that the user cannot understand).
- If the user says "no", then the system fails (or doesn't work).
- If the user says "yes", then its the user's fault for giving up their security.
-
The browser got this right.
-
Every other platform got this wrong.
-
From the beginning of computing it's been assumed that the program represents the owner of the machine, or at least the owner of the account.
-
The browser affirms that the program represents a site, that doesn't necessarily represent the user
- The web didn't anticipate that there can be more interests involved in a page than the user's and the site's interests.
- A malicious party can exploit coding conventions to inject malicious code
- That malicious code gets all of the rights of the site.
- This is known as the XSS problem
Once the attacker gets a foothold, it can obtain all of the scripts they need.
-
browsers implement a "Same Origin Policy", which limits the ability of the browser to get data from other servers
-
but there is no limit on how much program you can load from the most evil server in the world
The attacker can see everything the user sees. And stuff the user can't see (everything we transmit to the browser DOM)
Your server cannot detect that the request did not originate with your application.
If you're using SSL, the attacker gets access the this secure connection.
If instead your server constructs raw SQL queries internally, based on information received from the browser, then the attacker might get access to the database (yay)
Because SQL was optimized for SQL injection attacks. 😉
The user cannot detect that the request did not originate with your application.
Web standards require these weaknesses
Harm to customers. Loss of trust. Legal liabilities.
- the name 'XSS' is sort of a misnomer
- cross site scripting is highly desirable, we want sites (businesses and services) to cooperate with each other
- there are forms of the 'XSS' attack that don't require a second site
-
Content Security Policy now in browsers
- WSC Editor's Draft 15 April 2013
- most sites aren't using CSP
- barrier to CSP adoption: to use it, a lot of common practices become illegal
-
<iframe sandbox>
W3C Candidate Recommendation 17 Dec 2012 -
Browsers still unsafe by default
Advertising is a mashup
The most reliable, cost effective method to inject evil code is to buy an ad.
- The web stack is too complicated
- Too many languages, each with its own encoding, quoting, commenting, and escapement conventions.
- Each can be nested inside of each other.
- Browsers do heroic things to make sense of malformed content.
- Template-based web frameworks are optimized for XSS injection
- The JavaScript global object gives every scrap of script the same set of powerful capabilities.
- As bad as it is at security, the browser is a vast improvement over everything else.
- The browser distinguishes between the interests of the user and the interests of the site.
- It did not anticipate that multiple interests might be represented.
Anything that gets loaded from a third party must be trusted!
An ad or a widget or an Ajax library gets the same rights as the site's own scripts.
It can be repaired, becoming an object capability language.
- HTML grants power to confusers.
- HTML is easily confused.
- HTML is forgiving because webmasters were/are incompetent.
- HTML's API, the DOM, is also insecure.
It is up to the web developer to create secure applications on an insecure platform.
But there is hope in the 'Principle of Least Authority' and the object capability /actor model
Any unit of software should be given just the capabilities it needs to do its work, and no more.
Capabilities can be seen in the Actor Model that originated in 1973 at MIT that led to the invention of Scheme which led to higher order functions and actors.
- An actor is a computational entity.
- An actor can send a message to another actor only if it knows its address.
- An actor can create a new actor.
- An actor can receive messages.
- Web workers are actors.
- Web services are not...
Distributed, reliable services. http://www.waterken.com/
- An address of an actor is a capability.
- A reference to an object is a capability.
🅰️ is an Object- Object
🅰️ has state and behavior
objects contain state and behavior
- Object
🅰️ has a reference to Object🅱️
an object can have references to other objects
- Object
🅰️ can communicate with Object B... - because it has a reference to Object
🅱️
Objects can communicate with other objects they have reference to
- Object
🅱️ provides an interface that constrains access to its own state and references- Object
🅰️ does not get access to Object🅱️ 's innards - in JavaScript, must use
freeze
to guarantee limited access
- Object
- Object
🅰️ does not have a reference to Object🅾️ , so Object A cannot communicate with Object🅾️
In an Object Capability System, an object can only communicate with objects that it has references to.
A reference cannot be obtained simply by knowing the name of a global variable or a public class
- By creation
- By Construction
- By Introduction
If a function creates an object, it gets a reference to that object.
An object may be endowed by its constructor with references.
This can include references in the constructor's context and inherited references.
↓
🅰️ has references to both🅱️ and🅾️ 🅱️ has no references, so it cannot communicate with🅰️ or🅾️ 🅾️ has no references, so it cannot communicate with🅰️ or🅱️
- But,
🅰️ wants🅱️ and🅾️ to be able to communicate:
🅰️ calls🅱️ , passing reference to🅾️
🅱️ is now able to communicate with🅾️ - it has acquired the capability
If references can only by Obtained by Creation, Construction, or Introduction, then you may have a safe system.
- Arrogation
- Corruption
- Confusion
- Collusion
- Arrogation = to take or claim for oneself without right
- includes:
- global variables
- public static methods
- Standard libraries that grant powerful capabilities like access to the file system or the network or the operating system to all programs
- address generation (why c++ can never be a secure language)
- known URLs
It should not be possible to tamper with or circumvent the system or other objects
- why
Object.freeze
is so critically important
It should be possible to create objects that are not subject to confusion.A confused object can be tricked into misusing its capabilities.
- It must not be possible for two objects to communicate until they are introduced
- If two independent objects can collude, they might be able to pool their capabilities to cause harm
- Some capabilities are too dangerous to give to guest code.
- We can instead give those capabilities to intermediate objects that will constrain their power.
- For example, an intermediate object for a file system might limit access to a particular device or directory, or limit the size of files, or the number of files, or the longevity of file, or the type of files
Ultimately, every object should be given exactly the capabilities it needs to do its work and no more.
- Information Hiding (good system design) is enhanced when focused on Capability Hiding.
Intermediate objects, or facets can be very lightweight
- class-free languages can be especially effective
- The
Facet
Object limits theGuest
Object's access to thePowerful
object - The
Guest
Object cannot tamper with theFacet
object to get a direct reference to the dangerousPowerful
Object
Once you introduce an object, you can't ask it to forget it.
- You can ask, but you should not depend on your request being honored.
The Guest
Object has a reference to an Agency
Object. The Guest
asks for
an introduction to the Powerful
Object.
- The
Facet
might be a simple pass through
[Guest] ➜ [Agency]
| ╱ |
↓ ↙︎ ↓
[Facet] ➜ [Powerful]
When the Agency
wants to revoke the capability, it tells the Facet
to
forget its capability.
- The
Facet
is now useless to theGuest
[Guest] ➜ [Agency]
| ╱ |
↓ ↙︎ ↓
[Facet] [Powerful]
a Facet
can mark requests so that the Powerful
object can know where the request came from (gives us accountability)
- Very expressive
- Easy to construct
- Lightweight
- Attenuation: Power Reduction
- Revocation
- Notification
- Delegation
The best OO patterns are also capability patterns
Thinking about security (what's the least amount of power I can give this object to make sure it works correctly) can make the design of systems inherently better and easier, not harder.
- Facets can reduce the power of dangerous objects
- Most code should not be given direct access to dangerous things like
innerHTML
ordocument.write
- Instead of trying to guess if a piece of code can do something bad, give if safe capabilities instead
- Capabilities can aid in API design
Corrupting a single object results in a corrupted object, not a corrupted system
- The object capability model completely changes the economics of hacking
- in most system architectures, if you can confuse (corrupt) an object, you can get access to everything in the system
- in the Object Capability Model, if you confuse/corrupt (i.e. create an object incorrectly) an object, you get the capabilities of that object, but only that object
- confusion attack: see Function Challenge 11
Marc Stiegler
https://www.youtube.com/watch?v=eL5o4PFuxTY
Bugs/security exploits are a manifestation of confusion
Keep it simple. Keep it clean.
- be a minimalist, find the simplest solution possible
- code that is harder to understand is more likely to be exploited
- Good code is ultimately cheaper to produce than bad code, so we might as well always write good code.
- Good code is easier to reason about.
- Code that is difficult to reason about is more likely to be problematic
- Strict conformance to good style rules.
Don't get more intimate than sharing JSON payloads.
- It cannot and will not protect your interests
- Properly filter and validate all input
- Properly encode all output
- Context is everything
- Filter and encode for the correct context
XSS
Properly encode all of the non-literal pieces
- use good encoders and writers instead of just plus-ing things together
- Inconvenience is not security
- Identity is not security
- Taint ain't security
- Intrusion detection is not security
- "we gonna get this past the security guys because we've got to get this out"
Managing Asynchronicity with RQ and JSCheck
- Synchronous Functions
- Asynchronous functions
Do not return until the work is complete or failed
- useful because its easy to reason about its behavior over time
- when a synchronous function calls another synchronous function, the caller is suspended in time. Nothing advances until the callee returns
- not easy to reason about if we need things to happen at the same time
- Races
- Deadlocks
- Reliability
- Performance
- No rethinking is necessary
- Blocking programs are ok
- Execution continues as long as any thread is not blocked
- Stack memory per thread (ok)
- If two threads use the same memory, a race may occur
// two programs, will run possibly on their own thread
my_array[my_array.length] = 'a'; // program 1
my_array[my_array.length] = 'b'; // program 2
// race condition 1: where we don't know order of execution
// ['a', 'b']
// ['b', 'a']
// race condition 2: where we lose "half of our data" (!)
// ['a']
// ['b']
What's happening here?
my_array[my_array.length] = 'a'; // thread 1
my_array[my_array.length] = 'b'; // thread 2
// thread 1 is implemented like this:
length_a = my_array.length;
my_array[length_a] = 'a';
if (length_a >= my_array.length) {
my_array.length = length_a + 1;
}
// thread 2 is implemented like this:
length_b = my_array.length;
my_array[length_b] = 'b';
if (length_b >= my_array.length) {
my_array.length = length_b + 1;
}
- We cannot control what order these will execute in
- One possible ordering: both capture the length at the same time:
- (whichever one runs second will probably win out, and my_array will end up with one item)
length_a = my_array.length; // thread 1
length_b = my_array.length; // thread 2
my_array[length_a] = 'a'; // thread 1
if (length_a >= my_array.length) { // thread 1
my_array[length_b] = 'b'; // thread 2 - executes during thread1 loop
my_array.length = length_a + 1; // thread 1
}
if (length_b >= my_array.length) { // thread 2
my_array.length = length_b + 1; // thread 2
}
- it can be even worse than this, since each of the statements above can expand in to multiple machine language statements, which we don't know how will interleave, and each of those statements can expand into multiple micro instructions which order cannot be guaranteed.
-
Used to mitigate race conditions
-
Allows only one thread to be running in a critical section of memory at a time
-
this used to be operating system stuff
-
but has leaked into applications because of networking and multi-cores
- accessing the network is slow, we want to have stuff happening when slow stuff is occurring
- CPU makers give us more cores, which we don't know how to use
- most of what we do is not parallel, but serial
- semaphore
- monitor
- rendezvous
- synchronization
-
Only one thread can be executing on a critical section at a time
-
all other threads wanted to execute the critical section are blocked
-
if threads don't interact, then the program runs at full speed
-
if threads do interact, then races will occur unless mutual exclusion is employed
- Return immediately (almost no passage of time). Success or failure will be determined somehow in the future.
- turns as in turns in a game of chess, one player doesn't touch any pieces until the other players turn is done. Similar thing is happening with events and functions.
- A turn is started by an external event, such as the delivery of a message, completion of an asynchronous request, a user action, or the ticking of the clock
- A callback function associated with the event is called. It runs to completion.When it returns, the turn ends.
- No need for threads. No races. No deadlocks.
- Never block.
- Never wait.
- Finish fast.
Any code that must do any of these things must be isolated and run in a separate process. It is not allowed to run in the turn system.
- Event loops are used in Turn-based systems
- Completely free of races and deadlocks
- Only one stack
- Very low overhead.
- Resilient. If a turn fails, the program can still go on.
- Programs must never block.
- Turns must finish quickly
- Programs are inside out! (aka different style of program design than usual)
- Turn based. No pre-emption.
- Associate events with actions.
- Easy (beginners can do it)
- How User Interfaces have been always implemented
- JavaScript is moving to the server.
- What servers do is quite different from what browsers do.
- node.js implements a web server in a JavaScript event loop
- It is a high-performance event pump
- It has asynchronous file I/O that is non-blocking
fs.readFile(filename, encoding, function (err, data) {...})
- Everything is (or can be) non-blocking.
- Except:
- some synchronous functions
- require (is also blocking) (fixed in es6's module system)
- Except:
- Instead of events (browser), servers are message driven, message queue
- Actor-like
- Simple events don't fit:
- Sequential
- A sequence of requests, each dependent on the result of the previous
- Naive approach: deeply nested callbacks (brittle, low-performance, hard-to-maintain)
- Parallel
- Do a bunch of independent things (request goes to several systems at the same time, only wait for the slowest) (how to deal with unexpected return times, orders?)
- Naive approach: wastes time, increases latency
- Limited time
- SLAs or other polices that require a response within a certain amount of milliseconds
- If we don't get a response in time, go to Plan B (can't just let the request hang)
- Cancellation
- If we go to Plan B, how do we stop unnecessary work? (not easy to do in deeply nested event handlers)
- Sequential
- Futures (Dataflow and LISP)
- a an object that represents something that isn't knowable yet, but might be in the future
- you can begin to interact with the future object, eventually it will communicate your interest to whatever the answer turns out to be
- Promise
- Influenced by Futures, have now reached JavaScript
- Monads
- Arrows
- RX
- Microsoft: reactive extensions, allows for composing of event streams (badly documented)
- influenced Functional Reactive Programming (FRP): Flapjax, bacon.js, elm
- RQ
- A small JavaScript library (by Doug Crockford) for managing asynchronicity in server applications
RQ.sequence(requestors)
RQ.parallel(requestors)
RQ.parallel(requestors, optionals)
RQ.race(requestors)
RQ.fallback(requestors)
-
Takes an array of requestor functions, calls them one at a time, passing the result of the previous requestor to the next requestor
-
implements continuation passing style: at each step we don't return the result, instead we pass the result to the next function in the sequence
-
RQ.sequence
returns a function, when that function gets called with a provided callback, it will call the first function in the sequence with that callback asan argument
getNav = RQ.sequence([
read_file(file_name),
getPreference,
getCustomNav
]);
- Takes an array of requestor functions, calls them one all at once, and gives an array of results
- not adding parallelism of JavaScript, just utilizing the natural parallelism of the universe
getStuff = RQ.parallel([
getNav,
getAds,
getMessageOfTheDay
]);
- Also takes an optional array of optional requestors.
- Their results will be included if they can be obtained before the required requestors finish.
getStuff = RQ.parallel([
getNav,
getAds,
getMessageOfTheDay
], [
getHoroscope, // Will not wait for these, if these don't complete before
getGossip // the main three, they will be cancelled.
]);
- Takes an array of requestor functions, calls them one all at once, and gives the result of the first success
getAds = RQ.race([
getAd(adnet.klikHaus),
getAd(adnet.inUFace),
getAd(adnet.trackPipe)
]);
- Takes an array of requestor functions and gives the result of the first success
- could use a race, but hierarchy allows us to go to the remote DB as a last resort
getWeather = RQ.fallback([
fetch("weather", localCache),
fetch("weather", localDB),
fetch("weather", remoteDB)
]);
RQ.parallel([
RQ.sequence([
widget('Seq A1'),
widget('Seq A2'),
widget('Seq A3')
]),
RQ.sequence([
widget('Seq B1'),
widget('Seq B2'),
widget('Seq B3')
]),
widget('C'),
RQ.race([
widget('Race D1'),
widget('Race D2'),
widget('Race D3')
]),
RQ.fallback([
widget('Fall E1'),
widget('Fall E2'),
widget('Fall E3')
])
], [ // optional set
RQ.sequence([
widget('Opt Seq O1'),
widget('Opt Seq O2'),
widget('Opt Seq O3')
]),
RQ.sequence([
widget('Opt Seq P1'),
widget('Opt Seq P2'),
widget('Opt Seq P3')
]),
widget('Opt Q'),
RQ.race([
widget('Opt Race R1'),
widget('Opt Race R2'),
widget('Opt Race R3')
]),
RQ.fallback([
widget('Opt Fall S1'),
widget('Opt Fall S2'),
widget('Opt Fall S3')
])
])(show);
- RQ also takes timeouts
- can indicate that something needs to complete successfully in this much time, otherwise it will fail
RQ.sequence(requestors, millisecond)
RQ.parallel(requestors, millisecond)
RQ.parallel(requireds, milliseconds, optionals, untilliseconds)
RQ.race(requestors, milliseconds)
RQ.fallback(requestors, milliseconds)
- untilliseconds = gives the optional set a certain amount of time until it gets cancelled, in case the main ones finish early
- Any requestor can optional return a cancel function
- A cancel function, when called, will attempt to cancel a request
- No guarantee that the cancellation will happen before the request completes (bc of possible network races, etc.)
- Cancellation is intended to stop unnecessary work. It does not undo.
- requestor
- A function that can execute a request
- callback
- A continuation function that will be passed to a requestor
- factory
- A function that takes arguments and returns a requestor function
- cancel
- A function returned by a requestor that may be used to cancel a request