[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(+[![]]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+!+[]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[!+[]+!+[]])+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]])()((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[+!+[]+[!+[]+!+[]+!+[]]]+[+!+[]]+([+[]]+![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[!+[]+!+[]+[+[]]])
Javascript is a programming language that conforms to the ECMAScript specification. JavaScript is high-level, often just-in-time compiled, and multi-paradigm. It has curly-bracket syntax, dynamic typing, prototype-based object-orientation, and first-class functions.
Wikipedia
Since all modern browsers and nodejs >=14.0.0 support ES6 Modules, we will use the es module syntax
Import declarations to import functions and declarations from other modules
Function declarations
Class declarations
Export of declarations so to be used from other modules
Turn javascript engine in strict mode
Function declaration
Class declaration
external javascript can be loaded with the src
attribute on the script
element
Javascript can be inlined within a script
element. If you use the es module
syntax you have to set the attribute type
to module
on the script element
script elements can be inserted everywhere in the HTML document and will be evaluated as soon as the parser processes the element
node ./myfile.js
node -e 'console.log("test");'
node --input-type=module myfile.js
node ./myfile.mjs
node ./myfile.js
node -e 'console.log("test");'
node --input-type=module myfile.js
node ./myfile.mjs
Load a javascript file from the filesystem
Historically nodejs used its own module system called "commonjs" every js file is interpreted as a commonjs module
node ./myfile.js
node -e 'console.log("test");'
node --input-type=module myfile.js
node ./myfile.mjs
evaluate the javascript passed in the argument
node ./myfile.js
node -e 'console.log("test");'
node --input-type=module myfile.js
node ./myfile.mjs
Load a javascript file from the filesystem and interpret the file as a es module
node ./myfile.js
node -e 'console.log("test");'
node --input-type=module myfile.js
node ./myfile.mjs
Load a javascript file from the filesystem and interpret the file as a es module
On nodejs files with the .mjs
extension are treated as module files,
whereas file with the .cjs
extension are treated as commonjs files
typeof true === 'boolean'
boolean numbers represent only two states and are expressed with the expression true
and false
With the global Boolean
function it is possible to convert other types to
booleans
typeof 'test' === 'string'
Javascript distinguishes between the primitive type string
and the object
type
String
A string holds a sequence of characters, javascript supports utf-8 and utf-16 characters. .length
will return the number of characters in a string not the byte length
typeof 5 === 'number'
Same as for strings, JavaScript distinguishes between the primitive type number
and the object type Number
JavaScript only uses floating point numbers, Integers are represented as floating point numbers without a fraction
Don't blame JavaScript that 0.1+0.2 = 0.30000000000000004
, blame
IEEE
754
typeof 5n === 'bigint'
BigInt
was introduced because it was not possible to safely use large
Integers
in JavaScript
typeof Symbol() === 'symbol'
The Symbol
function can be used to create an unique symbol value which can
be
used
as an property type on objects. For debugging purposes symbols can have a description
There are some "well known" symbols which can be used in javascript to to add additional functionality to your own objects, for example usage of the Symbol.iterator symbol as a property to make an object iterable
The Symbol.iterator
well known symbol can be used to make any object
iterable
An iterator property must return a defined data structure...
...then its possible to iterate over the object with a loop
More on iterators in a later chapter
typeof undefined === 'undefined', typeof null === 'object'
In javascript there are two similar values describing "no value", null
and
undefined
In this example test is undefined
because it is declared but not yet
initialized...
...after initializing it with null
the value is null
...
...after assignment to a real value the value stored in variable test
is
the
number 1
Given we have a defined object with properties...
... the property prop
evaluates to the string value "test" ...
... the property prop2
, because it is not defined evaluates to the
value
undefined ...
... and the property nullProp
, because it is initialized with null
evaluates to null ...
... after deleting the property prop
on our object ...
...property prop
will evaluate to undefined because it is not defined on
our
object
Javascript has 3 keywords to declare a variable var
, let
and const
which behave different
var
was the only way to declare variables up to ES2015
let
lets you declare mutable variables which can be reassigned
const
lets you declare immutable variables which can not be reassigned
var
in non module javascript files in the root scope will
create the variable on the global object (window
on browsers)
let
in non module javascript files in the root scope will be
accessible in following scripts
const
in non module javascript files in the root scope will
be accessible in following scripts
var, let and const
assignments in module javascript files will only
be accessible within the module
as a rule of thumb, always prefer let
over var
and
const
over let
let
and const
assignment can have the nice side
effect that a new variable is created for every loop iteration, so its possible to keep the iterated
value
in a closure scope
The reason for the following error
Uncaught ReferenceError: Cannot access '<variable>' before initialization
Definition of hoistHoisting on MDN
transitive verb
1: LIFT, RAISE
especially : to raise into position by or as if by means of tackle
hoist a flag
function
declarations will be hoisted in scope
var
will be hoisted in scope but not initialized
let
and const
will not be hoisted
let
and const
Operator | Example | Same as |
---|---|---|
= | x = y | x = y |
+= | x += y | x = x + y |
-= | x -= y | x = x - y |
*= | x *= y | x = x * y |
/= | x /= y | x = x / y |
%= | x %= y | x = x % y |
**= | x **= y | x = x ** y |
Operator | Name | Description |
---|---|---|
+ | Add | Add two numeric values (concatenation for strings) |
- | Subtract | Subtracts two numeric values |
* | Multiply | Multiplies two numeric values |
/ | Divide | Divides two numeric values |
** | Exponentiation | Applies the exponent to a numeric value |
++ | Increments | Increments a value |
-- | Decrement | decrements a value |
% | Modulo | Division Reminder |
String can be concatenated with the +
operator
Operator | Name | Example |
---|---|---|
&& | Logical AND | (x == 5 && y == 5) |
|| | Logical OR | (x == 5 || y == 5) |
! | NOT | !(x == 5) |
Operator | Description | Sample |
---|---|---|
== | equal to | 1 == 1 (true) |
=== | equal value and equal type | "5" === 5 (false) |
!= | not equal | "1" != "2" (true) |
!== | not equal value or not equal type | "1" !== "2" (true) |
> | greater than | 1 > 0 (true) |
< | less than | 1 < 0 (false) |
>= | greater than or equal to | 1 >= 1 (true) |
<= | greater than or equal to | 1 <= 1 (true) |
Operator | Name | Description |
---|---|---|
& | AND | Sets each bit to 1 if both bits are 1 |
| | OR | Sets each bit to 1 if one of two bits is 1 |
^ | XOR | Sets each bit to 1 if only one of two bits is 1 |
~ | NOT | Inverts all the bits |
<< | Zero fill left shift | Shifts left by pushing zeros in from the right and let the leftmost bits fall off |
>> | Signed right shift | Shifts right by pushing copies of the leftmost bit in from the left, and let the rightmost bits fall off |
>>> | Zero fill right shift | Shifts right by pushing zeros in from the left, and let the rightmost bits fall off |
JavaScript is a dynamically typed language, if you compare different types JavaScript tries to
make
sense of
the types for you, this is the reason why [] == ''
evaluates to true
This behavior is called implicit type coercion and alway happens if you apply an operator (+, -, ==, ...) to values of different types
This will evaluate to true
because javascript will try to convert the
string to
a number and checks for equality
This will evaluate to false
because javascript will try to convert the
string
to a number (NaN), and boolean to a number (1) and check for equality
This will evaluate to true
because javascript will try to convert the
string to
a number (NaN), and boolean to a number (1) and check for inequality
This will evaluate to 11
because javascript will try to convert the number
to a
string ("1") and concatenate both
There are specific rules on how javascript will convert different types, these are explained in depth here
You can help the javascript engine to make sense of the types you want to work with with explicit coercion
In other languages this is also known as type casting, but it javascript a real conversion is happening
This can be rewritten to...
...this with explicit type coercion
For equality checks you can disable type coercion with the use of the "tripple equality operator" ===
or !==
This operator prevents implicit type coercion and javascript will compare the values of both operands and the identity for objects
this will evaluate to false
because the types and values don't match
this will evaluate to true
because the types and values match
this will evaluate to false
because the types match but the identity not
(two
separate object instances)
this will evaluate to true
because the types match but the identity
matches
(both are the same object instance)
Javascript only has 7 data types boolean, number, string, BigInt, Symbol, function and object
, to query the
datatype
of a value the typeof
operator can be used
instanceof
can be used to check if a object instance is of a specific type
Important never check against a typescript type with instanceof
, the operator only works on object instances
Object.isPrototypeOf
can be used to analyze the prototype chain
instanceof
should be used to check object instances
Object.isPrototypeOf
should be used to check prototype objects
with if
/else
you can control the control flow
of
your program to only execute specific statements if a specific expression evaluates to true
If/else control flows can also be an expression which return a specific value depending to what the if expression evaluates
There are two operators to assign a fallback value if the preceding value is "falsy" or "nullish"
The ||
operator will assign "default value"
to
the
variable result
if the value for value
is
"falsy"
falsy values are: false
,undefined
,null
,0
, ""
, this can
lead
to problems when for example the numeric value 0 is valid, in this case it would be overwritten by the
default value despite being valid
The ??
operator (null coalescing operator) will assign "default value"
to the variable result
if the value
for
value
is null
or undefined
this will prevent the problem with the ||
operator, the numeric
value 0
would not be overwritten with the defualt value
With the help of optional chaining you don't need to check if a specific path exists within nested object, this is a safe way to access nested object where you are not sure if specific properties exists on the objects
Switch-case statements are similar to if-else statements with the exception that multiple statements can be grouped together
The switch-case statement expects an expression...
... if the expression matches "EX1" or "EX2" the statements following "EX2" will be executed and after that the switch-case statement will be exited ...
... if the expression matches "EX3" the statements following "EX3" will be executed and after that the switch-case statement will be exited ...
... if no expression match the default branch will be executed and after that the switch-case statement will be exited
Every switch statement can also be written with if-if else-else statements
switch-case can have a disadvantage to if-else, all case branches share the same scope, so you have to be careful with reassigning and defining your variables
This is because only {}
create a block scope
For loops can iterate over a specific number range, this loop will start at 0
,
increment the identifier i++
after every execution, and loop as long as
i
is less than 100
There are three type how you can iterate in for loops
Iterate over a value range
Iterate over the values of an "iterable" type
Iterate over the indices of an array type
a while loop will loop until the given expression evaluates to false
The for loop example written in a while loop
a do while loop is the same as a while loop with the distinction that the expression is evaluated after every loop round
Javascript has two specific keywords continue
and break
to manipulate the loop flow
This will create an endless loop that will loop until the break condition i > 100 is triggered and skip all even numbers
typeof {} === 'object'
Objects are the base data type in Javascript for complex structures
different ways to create an empty object
objects are organized as dictionaries, objects can have properties which are either string identifiers or symbols
objects can have functions
functions defined in objects can access the current object with the this
keyword
Property names can also start with numbers or be a symbol
Properties can be added or removed after object creation
Properties can be defined with a setter or getter
Properties can be queried and enumerated with Object.getOwnPropertyDescriptors(obj)
With Object.defineProperty(obj, name, options)
you have more control over
the
property which you define
Set the value of the property
Make the property readonly or writable
Make the property enumerable with Object.entries()
or Object.keys()
Prevent redefining the property on the object, can also be done for all properties with Object.freeze()
or Object.seal()
Javscript has no concept for private properties on Objects (for classes it is possible), but there are several ways to simulate private properties
It is possible to hide information in closure scopes
Javascripts global Object
object is the base of all other objects defined
in
javascript (Array, Classes, etc..)
Destructuring can be used to destructure object properties in variables
The spread operator ...
can be used to shallow merge objects
the same can be done with the assign function const obj = Object.assign({}, obj1, obj2)
typeof [] === 'object', Array.isArray([]) === true
All real arrays extend the base object Array
Historically there are some objects which are "array-like", especially when working with the DOM API, an array like object is an object with numeric properties and a length property returning a number
All array-like objects can be converted to arrays with the static Array.from()
function this function can also be used to create and initialize
an
array
creating an array
push a new value to the end of an array
push a new value to the beginning of an array
pop the last value from an array
pop the first value from an array
reverse the array
Important: these commands manipulate the array instance in memory and do not create a new copy with the new values, this is important if multiple parts of your program access the same array, if one part reverses the array for example it is also done for all other parts accessing the same array
Javascript arrays allow a functional programming style to work with data with the builtin filter, map, reduce, concat
function
Filter all even numbers and create a new array with them
Make all values in the array to the power of 2
Add all values in the array together
This programming style is often controversially discussed[1]
Some love it for the clarity and immutable properties which functional programming gives you (not one assignment here), but if performance matters and you have a huge array an iterative approach is often more clear and faster
Loop over an array of numbers
filter out values which are not even
square the number
add the numbers together
both solutions have the same output but totally different runtime behavior, despite both are O(n) in complexity but the map-reduce solution has O(3n) runtime behavior and the loop solution only O(n)
For huge datasets loops, especially paired with loop tiling can have significant performance impacts: HTTP203 Episode on loop tiling
map, filter, reduce
will not work with async
functions using await
and iterators, if you work with async
functions you have to use regular loops, or wait for the Async Iterables Proposal
Typed arrays were introduced to ease the work with binary data were byte ordering and atomic byte access is important
Typed arrays work as a view on specific data which allows you to manipulate single bytes or words with full control of endianess
Destructuring can be used to destructure array entries into variables
The spread operator ...
can be used to merge arrays into a new one
the same can be done with the concat function const arrmerged = arr1.concat(arr2)
What happened?
TC39 wanted to add a "flatten" method to flatten an array, but mootools patched the built in array with a incompatible function with the same name
A Map
is a data structure to store data by a key, it allows easy
manipulation
and iteration
Add new data into a map
Retrieve data from the map
Check if a key exists
Delete a key
Create iterators to iterate over the values of a map
With a weak map its possible to store data on objects which can be cleanup up by the garbage collector
a weak map does not allow you to iterate over its contents and as soon as the garbage collector removes an instance used as key the data will be deleted from the weak map
const map = new Map();
const element = document.querySelector('.node');
map.set(element, '');
element.remove();
const map = new Map();
const element = document.querySelector('.node');
map.set(element, '');
element.remove();
This will leak memory, because the element removed from the dom is still referenced in the map as a key, as long as the map is not garbage collected the element will persist in memory
const map = new WeakMap();
const element = document.querySelector('.node');
map.set(element, '');
element.remove();
By changing the Map
to a WeakMap
the garbage
collector will also cleanup the entry in the map when removing the element and allow the element node to
be garbage collected
A Set can store simple values like an array but the order of entries is not preserved
An array is optimized for random access on a fixed data structure with a known index, a set is optimized for a growing data structure where the value is known
Create a new Set
Add a new value into the set
Check if value is present in a set
Remove a value from a set
Get an iterator for the set
Same as the WeakMap, the set holds only weak references to objects passed
Functions are first class citizens in javascript, this means you can assign them, use them as values, return them from functions and pass them as arguments
A function is considered pure if the functions output is only determined by its input arguments without side effects.
Math.cos(x)
is pure, because it will always return the same value
for
the same x
functions can be defined standalone or on objects
this
and functionsdepending on where and how a function is defined the keyword this
refers to
different objects
this
will be the global object (window on browsers)
this
will reference to the owning object
You can change the this
scope of functions with the builtin methods call
and apply
or bind
call
and apply
differ only by its
API
this will manipulate the scope of the function func
to use obj1
and return 'foo'
this will manipulate the scope of the function func
to use obj2
and return 'bar'
With the bind
function you can create a new function reference where the
scope
is always bound to a specific object
Create a new function reference of function func
which is fixed bound to
obj1
Executing the bound function obj1Func
will always use obj1
as this and return 'foo'
function
and =>
functions defined with the fat arrow function =>
will always
create a
function where this
is bound the the lexical scope
when executing the function func
the this
scope
within the function will point to the lexical scope which in this case is the global window object on
browsers
Applying a new scope to the function will have no effect
If we change the function 'func' to a regular function which lets this
point
to
the
owning object and return the fat-arrow function, it will use the this from the last function declaration
and
return the desired value
In javascript only class
and function
create a
lexical scope, this
within functions always references to the owning object
or
global scope, this
for fat-arrow functions always reference to the current
lexical scope
Important: JS modules do not have a global this object
loading js as a regular js file will have a global this
reference to window
loading js as a module js file will not have a global this
reference
to
window
both variable y
and variable test
are
accessible
from the function
The function returned when invoking test will retain its scope and all the variables accessible in it in its own closure scope
This can be used to hide implementation details or information, in this case the variable test is only
accessible via the function returned by func
This pattern for information hiding and lexical scope creation is often used in form of iife (Immediately invoked function expression)
javascript does not have a specific class type, the class
keyword is
syntactic
sugar for the prototype inheritance model of javascript
The class constructor
Class properties
Class function
javascript classes/constructor functions can be instantiated with the keyword new
You can access the properties and functions defined on the class
javascript classes also support inheritance
if you you extend another class you have to call the constructor of the parent (super) class
If your constructor is empty you can also omit the constructor on the class definition
javascript will lookup functions and properties within the prototype chain of the prototype object, thus allowing you to access functions and properties defined in super classes
with the super keyword you can explicitly call the function defined on a super class
class fields beginning with a #
are private class fields and only
accessible within the specific class
Important not all browsers support private class fields
Javascript classes (or prototype objects) are helpful if you want to instantiate multiple objects with the same structure, properties and functions
As already discussed in the Symbol
and Loops chapter, javascript has a
concept
of iterators. You can define your own iterators and make every object iterable
Most javascript built-in object support iterators, Array
, Map
, Set
,...
Lets create a simple number generator
To make any object iterable you have to define a function property with the fixed symbol Symbol.iterator
The iterator function must return a new object...
...which in turn has a function called next
...
...which returns an object with the properties value
which holds the actual
value and a boolean property called done
which indicates the end of the
iterator
Now you can iterate over your object with a for loop...
...or manually by using the iterator directly
The previous example can also be simplified with a generator function
function
declarations followed by an asterisk (*
)
are called generator functions
the yield
keyword will return a value a wait for the next next()
call
because yield
interrupts the loop until next()
is called, this infinite-loop will not block the javascript process
A return
keyword will return an iterator item with the done
flag set to true, thus closing the iterator
Now you can iterate over your object with a for loop...
...or manually by using the iterator directly
An exception is an error which can happen during the program execution - or runtime, to prevent a crash or undefined behavior it is important that you guard handle exceptions which happen within your program
Javascript supports exception handling with try - catch
blocks
The try-catch block
try-catch will execute the within the try
block
If an exception happens within the try block the catch block will be executed with the exception passed as an argument
If no exception happened or the exception was handled within the catch block the normal program flow will continue
Exceptions which happen somewhere in your code will bubble up the stack until the nearest catch block is found
you can "throw" your own exceptions (Errors) with the throw
keyword
a promise is a special object that a asynchronous function can return as a placeholder for a value which will resolve some time in the future
Promises can have three states
pending
the task which returned the promise is still executingresolved
the task finished with a resultrejected
the task finished with an errorPromises offer two methods to register a callback to react if the state changes
.then(callback)
the then callback will be called with the value which the
promised resolved.catch(callback)
the catch callback will be called with the reason when a
promise was rejected
Promise.resolve('value')
.then((value) => console.log(value))
.catch((reason) => console.error(reason);
Promise.resolve('value')
.then((value) => console.log(value))
.catch((reason) => console.error(reason);
Promise.resolve
will create a promise which will instantly resolve with the
given value
Promise.resolve('value')
.then((value) => console.log(value))
.catch((reason) => console.error(reason);
The callback provided in the then
method will be called with the value of the
resolved promise
Promise.resolve('value')
.then((value) => console.log(value))
.catch((reason) => console.error(reason);
The callback provided in the catch
method will be called with the reason why
the promise was rejected
Promise.resolve('value')
.then((value) => console.log(value))
.catch((reason) => console.error(reason);
In this case the promise will resolve with the value "value" and it will be displayed on the console
Promise.reject('error')
.then((value) => console.log(value))
.catch((reason) => console.error(reason);
In this case the promise will reject with the reason "error" and it will be displayed on the console
Most modern APIs which trigger asynchronous tasks will return promises nowaday
const promise = new Promise((resolve, reject) => {
// do asynchronous work
if(workDoneWithoutErrors) {
resolve(value);
} else {
reject(error);
}
});
You can also create your own promise object, by creating a new promise and pass a callback function to the
constructor with two arguments resolve
and reject
const promise = new Promise((resolve, reject) => {
// do asynchronous work
if(workDoneWithoutErrors) {
resolve(value);
} else {
reject(error);
}
});
If your work is done, you have to call the resolve
callback with a value
You can also omit a value, then the promise will resolve with void
const promise = new Promise((resolve, reject) => {
// do asynchronous work
if(workDoneWithoutErrors) {
resolve(value);
} else {
reject(reason);
}
});
If an error happen during your task, you have to call the reject
callback with
a string as a reason
const promise = new Promise((resolve, reject) => {
window.setTimeout(() => {
resolve('done');
}, 10000)
});
This promise will resolve after 10 seconds with the value 'done'
The global Promise
object also offers some helper function to work with
multiple promises
Promise.all([promise1, promise2])
.then((values) => {
const [valueFromPromise1, valueFromPromise2] = values;
})
.catch((reason) => {});
The Promise.all
helper will resolve once all promises in an array resolve or
reject if one of the promises was rejected
Promise.race([promise1, promise2])
.then((value) => {
// value of the promise which resolved first
})
.catch((reason) => {});
The Promise.race
helper will resolve once one of the promises in an array
either resolve or reject
async/await
keywords where introduced to make code utilizing Promises simpler
to read and write
doSomethingAsync((value, error) => {
if(error) {
// error happened
}
doSomethingOtherAsync(value, (error) => {
if(error) {
// error happened
}
// now do something
})
})
Async code before promises with nested asynchronous function calls
doSomethingAsync()
.then((value) => {
return doSomethingOtherAsync(value);
})
.then((value) => {
// do something
})
.catch((reason) => {})
Promises helped a bit because they could be chained... but the "callback hell" still persists
try {
const value = await doSomethingAsync();
const result = await doSomethingOtherAsync(value);
} catch(e) {
// error thrown;
}
The same promise based solution with async/await
try {
const value = await doSomethingAsync();
const result = await doSomethingOtherAsync(value);
} catch(e) {
// error thrown;
}
await
will stop the execution of the code and wait until the returned Promise
will either resolve or rejects
try {
const value = await doSomethingAsync();
const result = await doSomethingOtherAsync(value);
} catch(e) {
// error thrown;
}
once the promise resolved, the resolved value will be assigned to the variable "value" and javascript will continue the execution
try {
const value = await doSomethingAsync();
const result = await doSomethingOtherAsync(value);
} catch(e) {
// error thrown;
}
another await
will stop the execution again until the promise resolves or
rejects
try {
const value = await doSomethingAsync();
const result = await doSomethingOtherAsync(value);
} catch(e) {
// error thrown;
}
If one of the promises within the try block reject, the catch block is triggered and you can handle the exception
async function doSomething() {
const value = await asyncFunc();
return value;
}
function defined with the async
keyword always return a promise
Until Top Level Await is not in the
ES262 specifictaion await
can only be used within async functions
Javascript is single threaded so every work done will block the whole process
Javascript also offers a task concept with different hooks where you can enqueue tasks which are then run on a specified time
For real parallelism javascript offers WebWorkers
which can spawn real
parallel
processes
globalThis.setTimeout()
globalThis.setInterval()
globalThis.postMessage()
(Mutation/Resize)Observer
are executed herePromise.resolve().then(cb)
Mutation/ResizeObserver
globalThis.queueMicrotask(cb)
globalThis.requestIdleCallback(cb)
globalThis.requestAnimationFrame(cb)
Because there is only one eventloop, especially in the browser you should take care that the javascript execution time of all of your code within in round in loop does not exceed 16.6ms to maintain a stable 60fps
If you have work that needs a longer execution time try to split it or deffer it to Webworkers, ComLink is a good helper library
Historically all javascript was usually written in one big file and every file shared the same global scope which lead to a lot of problems with name clashes when multiple teams worked on the same project or when new libraries where introduced
If you have two libraries of javascript files which used the same namespace, they will overwrite each other depending on which javascript file gets loaded first
There where some tricks which could be used to scope your libraries, for example IIFEs
Because of all this different module systems began to emerge which where more or less incompatible
To tackle this problem the TC39 introduced ES modules with the 6th edition (ES2015) after a long discussion
asynchronous Module Definition
modules had a name (which could be the actual file name or a synthetic identifer) and export functions and properties, modules could be loaded asynchronous
Adopted by node.js
Modules are file based or synthetic (resolved by a module loader), module resolution is sequential, modules can export properties and functions
Available in node.js >= 14.0.0 and modern browsers
Modules are file based or synthetic (module loader/bundler needed), module resolution can be asynchronous
You can import specific function exported by other modules
You can import the whole namespace of another module
You can import a module only for its side effects
You can dynamically load modules (in this case top-level await must be supported) everywhere in your code
You can export variables, functions, classes and objects
All import statements (except dynamic imports) must always be at the beginning of your file
Export statements can be everywhere in your file but must be on the top scope
node.js | browser | |
AMD | ❌ | (✔) Only with library |
UMD | (✔) | (✔) Only with library |
CommonJs | ✔ | ❌ |
ES Modules | ✔ (>=14.0.0) | ✔ |
Typescript and Babel can transform and output different module types
import { export1 } from 'my-module'
import { export2 } from './my-module.js'
The browser does not support bare module imports until Import Maps are implemented
import { export1 } from 'my-module'
import { export2 } from './my-module.js'
The browser only supports relative imports with a defined extension
import { export1 } from 'my-module'
import { export2 } from './my-module.js'
nodejs supports bare module imports and imports without extensions, bare module imports will be resolved according to the node module resolution algorithm
Before you can deploy your project and let it run on the web browser and if you used bare module imports you have to use module bundlers to post process your javascript code, common used bundlers are Webpack, Rollup, esbuild or snowpack which will resolve your imports, statically analyze your modules and output performant bundles
The Technical Commitee 39 steers and decided on how to proceed with proposals to further developer ES262 and javascript, this is done in an open process where everybody can contribute to
All approved proposals which reached Stage 4 within a year will be summarized in a new ECMA Script Edition stating the current year
For some new features it is possible to make them available in older browsers with the help of "polyfills". New language features will mostly break in javascript engines in older browsers, in this case you need to use transpilers to transform this features to a lower ES edition. Notable transpilers are the Typescript Compiler and the Babel Project