JavaScript Primer

This is a very brief and superficial introduction to JavaScript.  The books in the references are much better and you should get all of them.  Here we only do client-side, browser-based JavaScript.  Also, we use ES6.  Although the class syntax is considered to be only syntactic sugar, we actually like sugar quite a lot.

JavaScript was invented by Brendan Eich in 1995.  Its syntax is somewhat inspired by Java, but it has very little to do with Java.  To understand some of the differences, the following comparison might be helpful:

Java JavaScript
Strongly-typed Loosely-typed
Static Dynamic
Classical Prototypal
Classes Functions
Constructors Functions
Methods Functions

The key differences being that Java has a statically typed system, whereas JavaScript has a dynamic type system.  And in JavaScript basically almost everythings seems to be a function.

With the advent of ES6 JavaScript has many new features.  Most people say it is just syntactic sugar, but in my opinion it is much more than that.  We will introduce a few of them here, such as classes, inheritance, closure, generators, and promises.

.

Introduction

The simplest way to run JavaScript is in the browser.  Use your favorite text editor, such as Notepad, Mousepad, TextEdit, or vi, and type the following into a file you can call "index.html":

<html>
    <head>
        <script type="text/javascript">
            alert("Hello world!");
        </script>
    </head>
</html>

Now simply open it with your browser and a pop-up box saying "Hello world!" should show up.  You could place the script tag also inside the body, but usually it is placed inside the head tag.

Next, let us consider functions, the bread and butter of JavaScript.

<html>
    <head>
        <script type="text/javascript">
                function run() {
                    document.write("Hello world!");
                }
        </script>
    </head>
    <body onload="run();">
    </body>
</html>

Here we actually use three features of JavaScript: functions, events and the DOM.  Functions are declared inside script tags, they start with the keyword "function", often have a name and have a function body, containing JavaScript code:

function run() {
    document.write("Hello world!");
}

The word "document" refers to an HTML document and is part of the browsers DOM.  DOM stands for Document Object Model and it is a JavaScript wrapper around all the different HTML elements in the browser.  Most important are the window and the document objects.

JavaScript has many events, in our example we use the onload event:

<body onload="run();">
</body>

It is triggered when the browser has finished loading the whole HTML page.  The above tells the browser to execute the JavaScript function run() after the HTML page has loaded completely.  Interestingly, for very large pages, JavaScript may actually start running before the whole page is loaded, this can cause problems when JavaScript tries to access parts that haven't even loaded.  Hence it is advisable to only start executing JavaScript code after the whole page has loaded.

Finally, let us talk about JavaScript files.  Instead of mixing HTML with JavaScript code, we can also place them in separate files.  In this case the "index.html" file would look like this:

<html>
    <head>
        <script type="text/javascript" src="script.js"></script>
    </head>
    <body onload="run();">
    </body>
</html>

and we put the JavaScript code inside a file called "script.js":

function run() {
    document.write("Hello world!");
}

This is clearly the preferred way of writing JavaScript code.  First, it separates look (HTML) from function (JavaScript).  Especially, when both get large, this is very helpful.  And second, it allows for reuse, meaning, we can include the same JavaScript file in many different HTML pages.

.

Popup Boxes

For simple interaction with the user, JavaScript has three kind of popup boxes: alert, confirm, and prompt.  The alert, we have seen already:

function showAlert() {
	alert("I am an alert box!");
}

The confirm box is often used if we want the user to verify or accept something.  When a confirm box pops up, the user will have to click either "OK" or "Cancel" to proceed.  If the user clicks "OK", the box returns true, if the user clicks "Cancel", the box returns false.

function showConfirm() {
    let b=confirm("Press a button");
    if ( b === true ) {
        document.write("You pressed OK!");
    } else {
        document.write("You pressed Cancel!");
    }
}

The prompt box is often used if you want the user to enter some value.  When a prompt box pops up, the user will have to click either "OK" or "Cancel" to proceed after entering an input value.  If the user clicks "OK" the box returns the input value. If the user clicks "Cancel" the box returns null.

function showPrompt() {
    let name = prompt("Please enter your name", "Harry Potter");
    if ( name !== null ) {
        document.write("Hello " + name + "!");
    }
}

.

Variables

Let us consider some simple variable declarations:

x;
var y;
let z;
const c;

The first one declares the variable x.  This maybe surprising, especially if you come from other programming languages, but JavaScript is a weakly typed language, meaning you do not have to declare variables at all.  Also, you do not specify a data type.  The JavaScript interpreter tries to do its best to figure this out by itself. 

This seems very convenient, but may lead to problems.  The most common has to do with typos: you slightly misspell a variable name.  In normal languages, the compiler would give an error message that the variable was not declared.  Not so in JavaScript: the interpreter thinks you want a new variable, and declares it for you automatically.  However, you can tell the interpreter not to do that with "use strict", which you should.

Therefore, preferrably we use the other declarations with the keywords var, let, and const.  The last one is always preferred, if a variable does not change, declare it a constant.  The difference between the other two is subtle.  Consider the following examples [2]:

function varTest() {
  var x = 1;
  {
    var x = 2; // same variable!
    console.log(x); // 2
  }
  console.log(x); // 2
}

function letTest() {
  let x = 1;
  {
    let x = 2; // different variable
    console.log(x); // 2
  }
  console.log(x); // 1
}

A variable declared with let is restricted to the scope in which it is defined, that is inside the curly braces.

In general, it is better to use let, unless one explicitely wants to create a global variable.  Because let, unlike var, does not allow you to create a global variable:

var x = "global";
let y = "global";
console.log(this.x); // "global"
console.log(this.y); // undefined

If you declare a variable within a function, the variable can only be accessed within that function. When you exit the function, the variable is destroyed. These variables are called local variables. You can have local variables with the same name in different functions, because each is recognized only by the function in which it is declared.

.

Data Types

In JavaScript you do not have much control over your data types.  However, they do exist internally.  The simplest one being

  • number,
  • string,
  • boolean,
  • and undefined.

You may notice, there is no difference between integers and floating point numbers.  JavaScript infers the actual data type from the declaration:

let x = 42;
let y = 42.0;
let z = "42";

So x is actually treated as an integer, y as a floating point number, and z as a string.  But as soon as it is needed, JavaScript will perform automatic type conversion.  However, only in one direction, that is from integer to floating point, integer to string and floating point to string, but not the other way round.

For this the conversion functions are helpful:

let x = parseInt("42");
let y = parseFloat("42.0");
let z = Math.trunc(42.0);

The last one converts a floating point number to an integer.  Always use trunc(), because floor() works differently on negative numbers.

Arrays and Objects

In JavaScript we also have complex data types, arrays and objects.  Arrays are declared with square brackets:

let arr1 = [];
let arr2 = [1, 2, 3, 4];

The first is an empty array and the second is an array with four elements.  Arrays in JavaScript can grow and shrink.

Similarily, objects are defined using curly braces, with a list of key-value pairs:

let cat = { name: "Garfield", age: 42 };

In this context also the JavaScript Object Notation (JSON) is interesting, since it is a simple way to represent these arrays and objects.  For instance the above cat would become:

"cat": { "name": "Garfield", "age": 42 }

Functions

Finally, unless you are used to C's function pointers, the following may seem a little strange:

let f = function() { alert("hi"); };
f();

Here we seem to define a variable called f, which actually is a function, not a variable.  The second line shows how f is being used.

typeof

You can ask the JavaScript interpreter what the type of a given variable is with the typeof operator.  For instance, the following code

let x = 42;
console.log( typeof(x) );

would output "number" to the console.  You may want to try the following examples to see how the typeof operator works.

function run() {
    let u1;
    console.log(typeof (u1));  // undefined
    let u2 = undefined;
    console.log(typeof (u2));  // undefined
    let x = 42;
    console.log(typeof (x));   // number
    let y = 42.0;
    console.log(typeof (y));   // number
    let z = "42";
    console.log(typeof (z));   // string
    let b = true;
    console.log(typeof (b));   // boolean
    let n = null;
    console.log(typeof (n));   // object
    let arr = [];
    console.log(typeof (arr)); // object
    let obj = { name: "Garfield", age: 42 };
    console.log(typeof (obj)); // object
    let f = function () { };
    console.log(typeof (f));   // function
}

Interesting maybe that null, arrays and objects are all considered objects.

.

Operators

JavaScript has the standard arithmetic and assignment operators: =, +, -, *, /, %, ++ and --.  The + operator can also be used to concatenate strings.  When adding a number and a string, the result will be a string. 

Comparison and logical operators are used to test for true or false.  We have the standard comparison operators

==      equal
!=      not equal
>       larger than
<       less than
>=      larger than or equal
<=      less than or equal

In addition, JavaScript has two more comparison operators: the exactly equals === and the not exactly equals !==.  They compare not only value but also type.  The following example shows the difference:

let x = 42;
let y = "42";
if (x == y) {
    console.log("x and y are equal");
}
if (x === y) {
    console.log("x and y are exactly equal");
}

In general, always use the exactly equals operators. 

As for the logical operators again those work exactly as in Java or C++. 

As for the bitwise operators, strange things might happen here, and they are slow.  So no point in using them.

.

Conditional Statements

As for conditions, JavaScript has the standard if-else statement:

let dt = new Date()
let time = dt.getHours()
if (time < 10) {
    alert("Good morning");
} else if (time > 10 && time < 16) {
    alert("Good day");
} else {
    alert("Good evening");
}

In addition, there is also the standard switch statement which works as expected.

.

Loops

JavaScript has the standard for-loop:

for (let i = 0; i < 10; i++) {
    console.log(i);
}

Important here is the let, because if you forget it, JavaScript won't complain, but it will create a global variable i for you, which is most likely not what you intended.  There is also the while-loop and the do-loop.

However, we have two more loops in JavaScript:  the for...in loop and the for...of loop. 

for...in Loop

The for...in loop is used in connection with objects, when you want to iterate through all the properties of an object.

let cat = { name: "Garfield", age: 42 };
for (const property in cat) {
    console.log(property + ": " + cat[property]);
}

for...of Loop

The for...of loop is used when you want to iterate over an iterable object. Iterable objects are Arrays, Strings, TypedArrays, Maps, and Sets, for instance.

let arr = [1, 2, 3, 4];
for (const element of arr) {
    console.log(element);
}

JavaScript also has the break and continue statement, but if possible those should be avoided.

.

Functions

As we have seen, functions are at the core of JavaScript.  In JavaScript almost everything is a function.  Functions can be called by events or by other functions.  Functions can have parameters and they can be anonymous.  There can also be functions inside functions.

JavaScript distinuishes between function declaration and function expression.

Function Declaration

This is most likely the way you have declared functions before.  Here the function has a name and parameters, they can also return something, and they exist on the global scope.

function run() {
    print("hi there");
}

function print(msg) {
    console.log(msg);
}

Function Expression

Function expressions on the other hand look like they are assigned to a variable.  A function expression is only available after it has been declared, and it is not neccessarily of global scope.

function run() {
    const print = function (msg) {
        console.log(msg);
    }
    print("hi there");
}

Notice the space between the keyword function and the parameter list ().  This is convention.

Function expressions are also called anonymous functions.  To see why, first notice that in JavaScript functions can also be passed as parameters to other functions.  We declare two functions, createMsg() and print(), and we pass the result of one into the other:

function run() {
    const print = function (msg) {
        console.log( msg );
    }

    const createMsg = function () {
        return "hi there";
    }

    print(createMsg());
}

We can rewrite this with an anonymous function:

function run() {
    const print = function (f) {
        console.log( f() );
    }

    print(function () {
        return "hi there";
    });
}

You may notice that the createMsg() function has disappeared, the code became shorter, but not neccessarily easier to read.

Parameters

In other languages you may have seen something called overloading, where you have two functions with the same name, but different types or number of parameters.  Since JavaScript is weakly typed that does not make a lot of sense, and therefore is not possible.  JavaScript's solution to the problem is as simple as it is ingenious: simply define the function with the maximum number of parameters, but when calling it, you do not have to give all parameters. 

function run() {
    print("hi there", "how are you?");
    print("hi there");
}

function print(msg1, msg2) {
    console.log(msg1);
    console.log(msg2);
}

 

The first version behaves as expected, whereas in the second version, the parameter msg2 is undefined.  However, the code just runs fine.  If you do not like things to be undefined, which is perfectly understandable, you can set parameters to a predefined value:

function print(msg1, msg2 = "empty") {
    console.log(msg1);
    console.log(msg2);
}

.

Error Handling

Because JavaScript is a weakly typed, interpreted language, it is very easy to use, but it is also very easy to make mistakes.  Fortunately, it has a pretty elaborate error handling mechanism, using the try catch syntax.

try catch

For instance, assigning a variable to something that does not exist, will cause a ReferenceError (assuming you use "use strict"):

function run() {
    let x = y;
}

If you want to catch this error, you would surround it with a try catch block,  The difference is: without our program will crash, whereas with it will print an error message, and continue to run:

function run() {
    try {
        let x = y;
    } catch (error) {
        console.log("An error occured: " + error);
    }
}

The error object contains information about what kind of error occured, and we can decide what to do next depending on the error.

In JavaScript, there are many errors, such as TypeError, RangeError, ReferenceError, and SyntaxError:

function run() {
    // TypeError
    try {
        let fritz;
        fritz.setColor(Color.RED);

        let num = 1;
        num.toUpperCase();

    } catch (e) {
        console.log(e);
    }

    // RangeError
    try {
        let b = new Array(-1)

    } catch (e) {
        console.log(e);
    }

    // ReferenceError
    try {
        let x = 3;
        x = x + y;

    } catch (e) {
        console.log(e);
    }

    // SyntaxError
    try {
        eval("let s = 'hi");

    } catch (e) {
        console.log(e);
    }
    console.log("still running...");
}

Interesting maybe that what would be errors in other languages, are no problem for JavaScript:

function run() {
    try {
        // ArithmeticException
        let x = 5 / 0;
        console.log(x);        // Infinity

        // NumberFormatException
        let y = parseInt("five");
        console.log(y);        // NaN

        // ArrayIndexOutOfBoundsException
        let eggs = [0, 1, 2, 3];
        console.log(eggs[5]);  // undefined

    } catch (e) {
        console.log("No errors here");
    }
    console.log("still running...");
}

throw

If you want to, you can also throw errors.  For instance,

if (!locateKarel()) {
    throw Error("Could not locate Karel in the world");
}

would throw an error, which then should be caught with some try catch.

"use strict"

While we are at errors: recall that in JavaScript we are not required to declare variables.  This can lead to problems:

//"use strict";  // uncomment this line

function run(() {
    let fritz = 5;
    frits = 4;
    console.log(fritz);
}

In the third line, we want to reassign a new value to the variable fritz.  But we made a typo, and hence instead of a reassignment, we declared a new variable named frits.  JavaScript does not care, there is no error here.  To avoid these kinds of stupid mistakes, it is highly recommended that you start all of your JavaScript code with the "use strict" keyword.  Adding it, will result in an error being thrown.

.

DOM

JavaScript per se is independent of the Document Object Model (DOM).  But as soon as we say things like

document.write("Hello World");
console.log("hi there");

we use the DOM.  Both document and console are part of the browser, one being the HTML document displayed, the other the browser's console.  For JavaScript they are just objects with certain properties and methods.  So DOM is just an abstraction of the browser, and whenever we want to interact with the browser, we do that via the DOM.

The most important objects the DOM provides are the console, which is mostly used for debugging, the document, which allows use to modify the HTML shown, and the window object, which gives us information about the browser window, for instance its size.  As an example for what the window object might be good for consider the following code:

function main() {
    alert("Done loading!");
}
window.onload = main();

The last line tells the browser to call the main() function, once the page has completely loaded.

To see why the document object is useful consider the following two examples.  Assume we have the following HTML document:

<html>
    <head>
        <script type="text/javascript" src="script.js"></script>
    </head>
    <body onload="run();">
        <textarea id="hans">Hi there</textarea>
    </body>
</html>

where we have a text area element, which has the id named "hans".  We can access this element with the getElementById() method:

function run() {
    const element = document.getElementById("hans");
    console.log(element.innerHTML);
}

We can also add, modify, and remove HTML elements.  Assume we have an empty HTML document and we want to add a text area.  The following code would just do that:

function run() {
    // create text area
    let textarea = document.createElement('textarea');
    textarea.id = 'hans';
    textarea.value = 'Hi there';
    textarea.rows = 10;
    textarea.cols = 32;
    textarea.readOnly = true;

    // add text area to body
    const _body = document.getElementsByTagName('body')[0];
    _body.appendChild(textarea);

    textarea.focus();
}

This should give you an impression of the power of the DOM.

.

Events

Interaction with the user usually happens via events.  But there are also events coming from the browser, such as the onload event, we have already seen. Examples of events are

  • loading of a web page or an image,
  • a mouse click or a keystroke,
  • submitting an HTML form,
  • or a timer event.

We need to tell the browser which events we are interested in, and what to do when the event occurs.  This can be done in the HTML or via JavaScript. 

An HTML example we have seen already:

<html>
    <head>
        <script type="text/javascript" src="script.js"></script>
    </head>
    <body onload="run();">
    </body>
</html>

Here we tell the browser that if the onload event occurs, that it should call the run() function.  We can do the same thing with JavaScript:

function run() {
    document.write("Hello world!");
}
window.onload = run();

If you are interested in key events, you would add an event listener:

function run() {
    document.addEventListener('keydown',
        function (ev) {
            console.log(ev.key + "," + ev.code);
        }
    );
}

In the same way you can add all kinds of events, such as keydown, keypress, click, onmousedown, change and many more.

Two interesting functions are the setTimeout() and setInterval() functions: the first allows you to wait a given time before a function is being called, and the second one calls a function repeatedly.

function run() {
    setInterval(sayHi, 1000);
}

function sayHi() {
  document.write("hi there<br/>");
}

Please, notice that inside the setInterval() function we are not calling "sayHi()", but instead "sayHi".  This makes a big difference, try it for yourself, and make sure you understand the difference!

.

eval()

Some people say eval() is evil, I personally think it is the greatest thing since sliced bread.  Sure it is dangerous, especially in a web language, but which other language lets you dynamically execute arbitrary new code during run-time?

function run() {
    let code = prompt("Enter some JavaScript code:", "document.write('hi');");
    eval(code);
}

If this does not shock you, nothing will [6].

.

Classes

One of the most welcome additions to JavaScript are classes.  The syntax looks quite similar to Java's, however, there are subtle differences:

function run() {
    const hansel = new Student("Hänschen");
    console.log(hansel.name);
    console.log(hansel.toString());
}

class Student {
    constructor(nm) {
        this.name = nm;
    }

    toString() {
        return "Student [name=" + this.name + "]";
    }
}

 

You will immediately notice that the constructor is actually called constructor.  Creation of objects using the new keyword also may look familiar. 

Interesting should be the line

this.name = nm;

which seems to indicate that there is an instance variable called name, but if we look, we do not find a declaration.  In fact, there is none, this is just JavaScripts way of defining instance variables, very efficient I would say.

Another interesting thing, there is no overloading in JavaScript, neither for the constructor nor for methods, we talked about this when we were talking about functions before. 

Closures

Let's return to the instance variables.  All instance variables defined above are public, meaning anybody can see and modify them.  This violates the principle of information hiding.  But not all is lost, because we have closures in JavaScript.  Assume our Student class should also have a read-only, private property id.  This is how we would implement this in JavaScript:

class Student {
    constructor(name, id = -1) {
        this.name = name;

        // closure: read only
        this.getId = (function () {
            let _id = id;
            return function () {
                return _id
            }
        })();
    }

    toString() {
        return "Student [name=" + this.name + ", id=" + this.getId() + "]";
    }
}

While we are at it, we can also use closures to implement an increment-only method:

class Student {
    constructor(name, id = -1) {
        this.name = name;

        ...

        // closure: increment only
        this.incrementCredits = (function (x) {
            let _credits = 0;
            return function (x) {
                if (x !== undefined && x > 0) {
                    _credits += x;
                }
                return _credits
            }
        })();
    }

    toString() {
        return "Student [name=" + this.name + ", id=" + this.getId()
      + ", credits=" + this.incrementCredits() + "]";
    }
}

If we call incrementCredits() without arguments, it just returns the current value of credits, if we call it with an argument, it will increment the credits by that amount.

Inheritance

We use the keyword extends to inherit from a parent class:

class Freshman extends Student {
	constructor(name, id = -1) {
		super(name, id);
	}

    toString() {
        return "Freshman [name=" + this.name + ", id=" + this.getId()
      + ", credits=" + this.incrementCredits() + "]";
    }
}

If you know Java this may look familiar to you.  As you can see we can override methods if we desire, but we don't have to.  And there is the super keyword to call the parent's class constructor or methods of the parent class.

Inheritance Chain

How about methods of the grandparent class, can we call those as well?  JavaScript has a method named getPrototypeOf() that allows you to get a handle to the respective parent, kind of neat:

function run() {
    const hansel = new Freshman("Hänschen", 12345);

    let me = Object.getPrototypeOf(hansel);
    console.log(me.constructor.name);           // Freshman
    let parent = Object.getPrototypeOf(me);
    console.log(parent.constructor.name);       // Student
    let grandParent = Object.getPrototypeOf(parent);
    console.log(grandParent.constructor.name);  // Object

    parent.test();
}

.

Polymorphism

If you lookup the meaning of polymorph, it says something like "an object or material which takes various forms".  We stay with our Student and Freshman classes.  If we have two students, hansel and gretel, hansel being a Freshman and gretel being a Student, then actually both of them are Students:

function run() {
    const hansel = new Freshman("Hänschen");
    polymorphic(hansel);
    const gretel = new Student("Gretchen");
    polymorphic(gretel);
    polymorphic("hi there");
}

function polymorphic(studnt) {
    console.log(studnt.toString());
}

Now in our polymorphic() method, we assume that the parameters passed in are Students, and therefore have the toString() method.

So using polymorphism in JavaScript is straight forward, the problem is enforcing it: because JavaScript is weakly typed, we can pass actually anything we want.  However, again not all is lost, because we can enforce proper datatypes using the instanceof operator:

function polymorphic(studnt) {
    if (studnt instanceof Student) {
        console.log(studnt.toString());
    } else {
        throw Error("Parameter is not of type Student!");
    }
}

.

Static Methods

JavaScript also has static methods, an example is the Random class:

class Random {
    static nextInt(low, high) {
        if (high !== undefined) {
            return parseInt(low + Math.random() * (high - low));
        } else {
            return this.nextInt(0, low);
        }
    }

    static nextDouble(low, high) {
        if (high !== undefined) {
            return low + Math.random() * (high - low);
        } else {
            return this.nextDouble(0, low);
        }
    }
}

We call these methods directly, i.e., no object needs to be instantiated:

function run() {
    console.log( Random.nextInt(2,7) );
}

Another example inspired from Java is System.currentTimeMillis():

class System {
    static currentTimeMillis() {
        return new Date().getTime();
    }
}
// const time = System.currentTimeMillis();

Don't use static to often, it does more harm than good.

.

Enums and Constants

A neat trick on how to create "constants" or something resembling enums is in the following way:

class Color {
}
Color.RED = 'red';
Color.GREEN = 'green';
Color.BLUE = 'blue';
Color.WHITE = 'white';
Color.BLACK = 'black';

Although neat, be careful, they are not constants!

.

Namespaces

Another thing kind of missing in JavaScript are namespaces.  But as usual there is a trick.  We define an empty object called Utils as a global variable, and then attach our class to it as property:

var Utils = {}
Utils.Random = class {
    static nextInt(low, high) {
        if (high !== undefined) {
            return parseInt(low + Math.random() * (high - low));
        } else {
            return this.nextInt(0, low);
        }
    }
}

When using it, it looks exactly like a namespace or package would:

function run() {
    console.log( Utils.Random.nextInt(2,7) );
}

But naturally, it is not.

.

Promise

Whenever you have to wait for something, most likely you will need a promise.  Let's look at two simple examples.

Assume you want something to happen once per second, that is you want a pause() method.  Then the following is an implementation using a Promise:

const pause = function (milliseconds = 500) {
    return new Promise(
        function (resolve) {
            return setTimeout(resolve, milliseconds);
        })
};

You would call this method in the following way:

async function run() {
    let i = 0;
    while (true) {
        console.log("waiting one second: " + i++);
        await pause(1000);
    }
}

Notice the async and await keywords: async tells us that the function run() is asynchronous, and the await tells us that at this point execution might stop until something happens.

Another nice example is asking the user for feedback [3]:

function promptForDishChoice() {
    return new Promise(function (resolve, reject) {
        const dialog = document.createElement("dialog");
        dialog.innerHTML = '<form method="dialog">' +
            '<select><option value="pizza">Pizza</option>' +
            '<option value="pasta">Pasta</option></select>' +
            '<button value="cancel">Cancel</button>' +
            '<button type="submit" value="ok">OK</button></form>'
		
        dialog.addEventListener("close", function () {
            if (dialog.returnValue === "ok") {
                return resolve(dialog.querySelector("select").value);
            } else {
                return reject(new Error("User cancelled dialog"));
            }
        });

        document.body.appendChild(dialog);
        dialog.showModal();
    });
}

async function run() {
    const choice = await promptForDishChoice();
    console.log('choice=' + choice);
}

To understand this, first look at the addEventListener() part: if the user clicked "ok" we return a resolve(), meaning the promise was resolved. Otherwise, we return a reject(), meaning the promise was rejected.

This shows very nicely the idea behind promises: a promise is in one of three states:

  • pending: this is the initial state, it is neither resolved nor rejected,
  • resolved: meaning that the operation was completed successfully, or
  • rejected: meaning that the operation has failed.

.

Generators

JavaScript is a synchronous language, meaning there is a single thread.  Events are an exception, so are timer and asynchronous web requests.  So obviously asynchronous programming seems possible in JavaScript.  A callback is also an example of asynchronous programming.  And generators are another.

A generator function is defined in the following way:

function* generator(i) {
    yield 1;
    yield 2;
    yield 3;
}

We notice that instead of returns it has yields.  We create a function expression generate and then call the next() method:

function run() {
    const generate = generator();
    console.log(generate.next().value);  // 1
    console.log(generate.next().value);  // 2
    console.log(generate.next().value);  // 3
    console.log(generate.next().value);  // undefined
}

We observe that after every call to next() we go from one yield to the next yield.  So in a sense yield is similar to return, but it keeps state, meaning it remembers where it left of last time, and continues from there when it is called again.  This may seem useless, but let's consider two more examples.

Assume we want a function that generates the sequence 0, 1, 2, 3, 3, 3, ... then we could do this with the following generator function:

function* generator(i) {
    for (let i = 0; i < 3; i++) {
        yield i;
    }
    return 3;
}

Or if we want a function that is called exactly four times, once per second?  We use the same generator function as above, but our run() method looks like this:

async function run() {
    const generate = generator();
    while (true) {
        let result = generate.next();
        console.log("next: ", result);
        await pause(1000);
        if (result.done) {
            break;
        }
    }
}

where we used the pause() method from above.  Notice that result.done is true when we hit the return statement in the generator [5].

.

Arrow Function

Another new feature of ES6 is the arrow function.  You will not see it much in my code, because for beginners it may be a bit confusing.  Consider the following code with arrow function:

const pause = (milliseconds = 500) => 
    new Promise(resolve => setTimeout(resolve, milliseconds));

Very concise, but maybe not so easy to understand on first sight.  The same code without arrow function:

const pause = function (milliseconds = 500) {
    return new Promise(
        function (resolve) {
            return setTimeout(resolve, milliseconds);
        })
};

It definitely is more code to write, but now it becomes clear that what you are doing here is declaring two anonymous functions [4].

.

References

[1] JavaScript: The Good Parts: Working with the Shallow Grain of JavaScript, Douglas Crockford

[2] let, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let

[3] Promise, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

[4] When should I use arrow functions in ECMAScript 6?, https://stackoverflow.com/questions/22939130/when-should-i-use-arrow-functions-in-ecmascript-6

[5] Generator: Using the function* Declaration in JavaScript, https://www.geeksforgeeks.org/using-the-function-declaration-in-javascript/?ref=rp

[6] eval(), https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval

.