Learn JS

Intro

This, a workbook of basic JavaScript learning, comprises some simple projects demonstrating ways of using functions, variables, arrays, control statements, and DOM manipulation.

We'll also cover some distinctions of modern Javascript, then dive deeper, looking at objects, classes and modules, and finish with some more advanced projects demonstrating asynchronous operations with HTTP requests.

Source code is included with each program, along with other illustrative code examples, snippets, and notes.


Need to know

You understand basic programming concepts and can mark-up a web document with HTML, CSS, and include JavaScript.

Sources and references

Code examples are rendered via prism.js which enables documentation links to be embedded in comments, like this:

"use strict"
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode
        

Data and Functions

Code


      

How it works

This excercise uses only variables and an array. No control statement is needed.

First we set up variables using the ES6 const and let keywords, instead of the generic var keyword used in previous versions of JS.
We use const to declare variables that will not change. Attempting to change them will throw errors. We use let for variables that will change.
There are six datatypes available (boolean, number, string, null, undefined, and objects).
With const, we assign two strings, and the HTMLElement objects we'll need. With let, we declare eightBall as undefined, set randomNumber to null and declare answer as an empty string.

We then have three functions, as ES6 function expressions:
init() I decided to set all the text in the script, keeping the html semantically generic, so it could be the structure of any 'game' with a button and an empty node to hold a response. I like how this function adds that layer of game-specific meaning to the bare bones of the html. The function also adds an event listener to the button. Note the callback passed to the listener hasn't been defined yet.

getAnswer() This is pretty self-explanatory. Because our eightBall variable was declared with let, we can assign it to anything we want, in this case an array. Let has block scope, which means this array will only be available within this function's curly braces. Elements in an array declared with const remain mutable. We can change the contents, but cannot reassign a new array or a different value. Arrays in js are objects, with indexed properties, and a generous collection of powerful methods this program doesn't even touch. It simply accesses one index defined by an random integer between 0 and 7 we've set using js's built in Math object. This could be done as one line, but broken down into 2 makes it very clear. If the array's length mutates, we could incorporate the array.length method into the one-line, which has been included commented out.

playEightBall() This function runs getAnswer() and assigns the return value to answer. It then messes with the style of the empty result DOM node, to provide some instant feedback. Then the answer is set and faded in. This delay covers cases when the same random answer is selected consecutively: without this it would seem as if nothing had occured.

const mottos = [
    'The world is not enough.',
    'Reductio ad absurdium',
    "Crows gather in the meadow where we meet.""];
const saying1 = famousSayings[0];
console.log(listItem);
console.log(famousSayings[2]);
console.log(famousSayings[3]);
/* output:
Fortune favors the brave.
Where there is love there is life.
undefined
* notice trying to access an array item beyond the end of the array produces an undefined variable */
//the following line would update the second item
famousSayings[1] = 'You cannot be serious";
        

The array object's built in methods include Array.sort The following program demos more methods:

let secretMessage = ['Learning', 'is', 'not', 'about', 'what', 'you', 'get', 'easily', 'the', 'first', 'time,', 'it', 'is', 'about', 'what', 'you', 'can', 'figure', 'out.', '-2015,', 'Chris', 'Pine,', 'Learn', 'JavaScript'];
    console.log(secretMessage.length);
    secretMessage.pop();
    console.log(secretMessage.length);
    secretMessage.push('to', 'program');
    secretMessage[7] = 'right';
    secretMessage.shift();
    secretMessage.unshift('Programming');
    secretMessage.splice(6, 5, 'know');
    console.log(secretMessage.join(' '));
        

Good stuff on arrays

Functions

Function declarations are simple enough



Function expressions use a different syntax, allowing anonymous functions, useful for passing in as arguments. Unlike function declarations, function expressions are not hoisted so they cannot be called before they are defined.

const plantNeedsWater = function(day){
  if(day === 'Wednesday'){
    return true;
  } else{
    return false;
  }
}
plantNeedsWater('Tuesday');
console.log(plantNeedsWater('Tuesday'));

ES6 introduces default parameters. The following code demostrates this, as well as embedded expressions using template literals.

function makeShoppingList(item1='milk', item2='bread', item3='eggs'){
  console.log(`Remember to buy ${item1}`);
  console.log(`Remember to buy ${item2}`);
  console.log(`Remember to buy ${item3}`);
}
makeShoppingList();

We can use babel to transpile modern js down into old-school js, for browsers that haven't caught up.

Helper functions: functions called within another function

  function monitorCount(rows, columns) {
    return rows * columns;
  }

  function costOfMonitors(rows,columns){
    return monitorCount(rows,columns) * 200;
  }

  const totalCost = costOfMonitors(5,4);

  console.log(totalCost);

Arrow functions can be used for function expressions:

const plantNeedsWater = (day) => {
  if (day === 'Wednesday') {
    return true;
  } else {
    return false;
  }
};

/*these can be refactored to make very concise function expressions
  known as concise body.
  Techniques:
    * No parenthesis are needed to enclose single parameters
    * A function body composed of a single-line block can dispense with:
      curly braces and the return keyword.
    * The block should immediately follow the arrow =>
    * This is referred to as implicit return.

  Using these techniques and the ternary operator
      plantNeedsWater() can be refactored to a single line:
*/

const plantNeedsWater = day => day === 'Wednesday' ? true : false;

Rock Paper Scissors

Enter your move and press 'Play!':




Higher order functions

Higher order functions accept other functions as arguments, and/or return functions as output.
The following code declares a stupid function, with a stupidly long name, using the const keyword, and then reassigns it to a more usable name.

const checkThatTwoPlusTwoEqualsFourAMillionTimes = () => {
  for(let i = 1; i <= 1000000; i++) {
    if ( (2 + 2) != 4) {
      console.log('Something has gone very wrong :( ');
    }
  }
}

//assign announceThatIAmDoingImportantWork without parentheses as the value to the busy variable.
//We want to assign the value of the function itself, not the value it returns when invoked.
const is2p2 = checkThatTwoPlusTwoEqualsFourAMillionTimes;
is2p2();
console.log(is2p2.name);

Here's a more realistic example from the faq for this excercise Let's say we have two functions, makeSubarray and isEven:

function makeSubarray (arr, select) {
   return arr.filter(select);
}
function isEven (n) {
   return n % 2 === 0;
}

const array = [0, 1, 2, 3, 4, 5];
//if we pass our array and isEven
makeSubarray (array, isEven); /* returns [0, 2, 4] */

So this gives us callbacks.
In the case of assigning functions to variables with shorter names, this can be helpful with libraries. If you only need to to use one of many named distance functions, assigning it:
const dist = EuclideanDistance;.

Litebrite

CYAN
YELLOW
MAGENTA

Objects

Objects are js's seventh datatype. The following code goes over the basics

/*spaceship object*/
let spaceship = {
  homePlanet: 'Earth',
  color: 'silver',
  'Fuel Type': 'Turbo Fuel',
  numCrew: 5,
  flightPath: ['Venus', 'Mars', 'Saturn'],
  'Active Mission': true,
  'Secret Mission' : 'Discover life outside of Earth.',
  passengers: null,
  telescope: {
    yearBuilt: 2018,
    model: "91031-XLT",
    focalLength: 2032
  },
  crew: {
    captain: {
      name: 'Sandra',
      degree: 'Computer Engineering',
      encourageTeam() { console.log('We got this!') },
     'favorite foods': ['cookies', 'cakes', 'candy', 'spinach'] },
    'chief officer': {
      name: 'Dan',
      degree: 'Aerospace Engineering',
      agree() { console.log('I agree, captain!') }
        },
    medic: {
      name: 'Clementine',
      degree: 'Physics',
      announce() { console.log(`Jets on!`) } },
    translator: {
      name: 'Shauna',
      degree: 'Conservation Science',
      powerFuel() { console.log('The tank is full!') }
        }
  },
  engine: {
    model: "Nimbus2000"
  },
  nanoelectronics: {
    computer: {
      terabytes: 100,
      monitors: "HD"
    },
    backup: {
      battery: "Lithium",
      terabytes: 50
    }
  }
};
/*access properties*/
/*using dot notation*/
let crewCount = spaceship.numCrew,
    planetArray = spaceship.flightPath;
/*using bracket notation
 *We *must* use bracket notation when accessing keys that have numbers, spaces, or special characters in them.*/
let propName =  'Active Mission';
let isActive = spaceship['Active Mission'];
console.log(spaceship[propName]);
/*properties are mutable*/
//change a prop
spaceship.color = 'glorious gold';
//add and set a prop
spaceship.numEngines = 8;

delete spaceship['Secret Mission'];
/*objects can have both objects and methods nested as properties of the parent object
 *the following accesses these properties*/
let capFave = spaceship.crew.captain['favorite foods'][0];
spaceship.passengers = [
  {name: 'Korv',
  seat: 1},
  {name: 'Mary',
  seat: 2}
];

let firstPassenger = spaceship.passengers[0];
/*passing by reference
 */
let greenEnergy = spaceship =>{
  spaceship['Fuel Type'] = 'avocado oil';
}

let remotelyDisable = spaceship =>{
  spaceship.disabled = true;
}

greenEnergy(spaceship);
remotelyDisable(spaceship);

console.log(spaceship);

//looping
for (let member in spaceship.crew){
  console.log(`${member}: ${spaceship.crew[member].name}`
)};

for (let member in spaceship.crew){
  console.log(`${spaceship.crew[member].name}: ${spaceship.crew[member].degree}`
)};

The this keyword, getters and setters

/*The this keyword
*/
const robot = {
  //the _ indicates a private prop, by convention
  _energyLevel: 100,
  //if a dev failed to follow the convention, and set the prop to a string, bugs would be introduced into the follwing method, which uses the prop
  recharge(){
    this._energyLevel += 30;
    console.log(`Recharged! Energy is currently at ${this._energyLevel}%.`)
  },
  //* key takeaway from the example below is to avoid using arrow functions when using 'this' in a method!
  checkEnergy () {
    console.log(`Energy is currently at ${this.energyLevel}%.`)
  },
  //we can flag errors caused by setting _energyLevel incorrectly with this getter method:
  get energyLevel(){
    if (typeof this._energyLevel==='number'){
      return 'My current energy level is '+ this._energyLevel;
    } else{
      return 'System malfunction: cannot retrieve energy level';
    }
  },
  //and we could create a setter method as follows
    set energyLevel(num){
    if (typeof num === 'number'&& num >= 0){
      this._energyLevel = num;
    } else{
      console.log('Pass in a number that is greater than or equal to 0')
    }
  }
}//end of robot obj
//this code will run fine
robot.checkEnergy();
//but this will produce unwanted side effects on the recharge function
//the developer has indicated the _energyLevel property as private
//she doesn't want it changed to another datatype, as in this example
robot._energyLevel='high';
robot.recharge();

Object factory functions, built in methods

/*a factory is a function used to create objects
*The robotFactory uses property value shorthand
*/
function robotFactory(model, mobile){
  return {
    model,
    mobile,
    functionality: {
      beep() {
        console.log('Beep Boop');
      },
      fireLaser() {
        console.log('Pew Pew');
      }
    }
  }
}

// To check that the property value shorthand technique worked:
const robot = robotFactory('P-501', false)
console.log(robot.model)
console.log(robot.mobile)
//destructured assignment we create a variable with the name of an object’s key that is wrapped in curly braces { } and assign to it the object.
const {functionality} = robot;
functionality.beep();
//object methods keys(), entries(), and assign()
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object#Methods_of_the_Object_constructor

const robotKeys = Object.keys(robot);

console.log(robotKeys);

// Declare robotEntries below this line:
const robotEntries = Object.entries(robot);

console.log(robotEntries);

// Declare newRobot below this line:
const newRobot = Object.assign({laserBlaster: true, voiceRecognition: true},robot);

console.log(newRobot);

Object projects

Mealmaker


Team stats

const team= {
  _players: [
    {
  		firstName: 'Korvin',
      lastName: 'M',
      age: 48
  	},
  	{
      firstName: 'James',
      lastName:'Eyre',
      age :29
    },
 	 	{
      firstName: 'Robin',
      lastName: 'Cook',
      age:  66
    }
  ],
  _games: [
    {
      opponent: 'Broncos',
      teamPoints: 34 ,
      opponentPoints: 22
    },
    {
      opponent: 'Harlem Heroes',
      teamPoints: 44,
      opponentPoints: 43
    },
    {
      opponent: 'London Giants',
      teamPoints: 44,
      opponentPoints:66
    }
  ],
  get players(){
    return this._players;
  },
  get games(){
    return this._games;
  },
  addPlayer(firstName, lastName, age){
    let player = {
      firstName: firstName,
      lastName: lastName,
      age: age
    };
    this.players.push(player);
  },
 addGame(opponentName, myPoints, opponentPoints){
    let game = {
      opponent: opponentName,
      teamPoints: myPoints,
      opponentPoints: opponentPoints
    };
    this.games.push(game);
  }
};

team.addPlayer('Steph', 'Curry', 'Age 28');
team.addPlayer('Lisa', 'Leslie', 'Age 44');
team.addPlayer('Bugs', 'Bunny', 'Age 76');
console.log(team._players);
team.addGame('Titans', 100, 100);
console.log(team.games);

Classes

The following is a basic class with a constructor and methods

class Surgeon {
  constructor(name, department) {
    this._name = name;
    this._department = department;
    this._remainingVacationDays = 20;
  }
  get name(){
    return this._name;
  }
  get department(){
    return this._department;
  }
    get remainingVacationDays(){
    return this._remainingVacationDays;
  }
  takeVacationDays(daysOff){
    this._remainingVacationDays = this.remainingVacationDays - daysOff;
  }
}//endof Surgeon class

const surgeonCurry = new Surgeon('Curry', 'Cardiovascular');
const surgeonDurant = new Surgeon('Durant', 'Orthopedics');

console.log(surgeonCurry.name);
surgeonCurry.takeVacationDays(3);
console.log(surgeonCurry.remainingVacationDays);

Inheritance

class HospitalEmployee {
  constructor(name) {
    this._name = name;
    this._remainingVacationDays = 20;
  }

  get name() {
    return this._name;
  }

  get remainingVacationDays() {
    return this._remainingVacationDays;
  }

  takeVacationDays(daysOff) {
    this._remainingVacationDays -= daysOff;
  }
  //a static method
  static generatePassword(){
    return Math.floor(Math.random()*10.000);
  }
}

class Nurse extends HospitalEmployee {
  constructor(name, certifications) {
    super(name);
    this._certifications = certifications;
  }
  get certifications(){
    return this._certifications;
  }
  addCertification(newCertification){
    this._certifications.push(newCertification);
  }
}

const nurseOlynyk = new Nurse('Olynyk', ['Trauma','Pediatrics']);
nurseOlynyk.takeVacationDays(5);
console.log(nurseOlynyk.remainingVacationDays);

nurseOlynyk.addCertification('Genetics');
console.log(nurseOlynyk.certifications)
console.log(nurseOlynyk.generatePassword);

Class Projects

Library


Ideas to build on this:

  • Add more properties to each class (movieCast, songTitles, etc.)
  • Create a CD class that extends Media.
  • In .addRating(), make sure input is between 1 and 5.
  • Create a method called shuffle for the CD class. The method returns a randomly sorted array of all the songs in the songs property.
  • Create class called Catalog that holds all of the Media items in our library.

School Catalogue


Ideas to extend this

  • Add more properties to each class (averageTestScores, schoolOverview, etc.)
  • Create a class called SchoolCatalog that holds a collection of schools. Create an instance of SchoolCatalog for primary, middle, and high schools.

Modules

/*modules.js
*modules can easily be made available*/
let Menu = {};
Menu.speciality = "Roasted Beet Burger with Mint Sauce";
module.exports = Menu;

let Airplane ={
  myAirplane: "StarJet"
};
module.exports = Airplane;
/*main.js
in another file, we can use require() to import the available modules*/
const Airplane = require('./modules.js');

function displayAirplane(){
	console.log(Airplane.myAirplane);
}

displayAirplane();
    
/*modules.js
* we can also wrap our data and functions in an object, and export by assigning the object to module.exports*/
let Airplane = {};
module.exports = {
  myAirplane: "CloudJet",
  displayAirplane: function(){
    return this.myAirplane;
  }
};
/*main.js*/
const Airplane = require('./modules.js');

console.log(Airplane.displayAirplane());

Modules in ES6

As of ES6, JavaScript has implemented a new more readable and flexible syntax for exporting modules. These are usually broken down into one of two techniques, default export and named exports.

/*modules.js
*named and default exports can be combined*/
export let availableAirplanes =[
  {name: 'AeroJet',
  fuelCapacity: 800,
  availableStaff: ['pilots', 'flightAttendents', 'engineers', 'medicalAssistance', 'sensorOperations'],
   maxSpeed: 1200,
   minSpeed: 300
  },
  {name: 'SkyJet',
  fuelCapacity: 500,
  availableStaff: ['pilots', 'flightAttendents'],
  maxSpeed: 800,
  minSpeed: 200
  }
];

export let flightRequirements = {
  requiredStaff: 4,
  requiredSpeedRange: 700
}
function meetsSpeedRangeRequirements(maxSpeed, minSpeed,requiredSpeedRange){
  let range = maxSpeed - minSpeed;
  if (range>requiredSpeedRange){return true;}else{return false;}

};
export function meetsStaffRequirements(availableStaff, requiredStaff){
  if (availableStaff.length>=requiredStaff){
    return true;
  }else{
    return false;
  }
};

export default meetsSpeedRangeRequirements;
    
/*
    The import keyword begins the statement.
    The keyword Menu here specifies the name of the variable to store the default export in.
    from specifies where to load the module from.
    './menu' is the name of the module to load. When dealing with local files, it specifically refers to the name of the file without the extension of the file.
*/
import {availableAirplanes, flightRequirements, meetsStaffRequirements} from './modules';
import meetsSpeedRangeRequirements from './modules';
function displaySpeedRangeStatus(){
  availableAirplanes.forEach(function(element){
    console.log(element.name + ' meets speed range requirements: ' + meetsSpeedRangeRequirements(element.maxSpeed, element.minSpeed, flightRequirements.requiredSpeedRange));
  });
}
function displayFuelCapacity(){
  availableAirplanes.forEach(function(element){
    console.log('Fuel Capacity of '+ element.name +': ' + element.fuelCapacity);
  });
}

function displayStaffStatus() {
  availableAirplanes.forEach(function(element){
    console.log(element.name + ' meets staff requirements: ' + meetsStaffRequirements(element.availableStaff, flightRequirements.requiredStaff));
  });
}

displayFuelCapacity();
displayStaffStatus();
displaySpeedRangeStatus();

Promises

A Promise is an object representing the eventual completion or failure of an asynchronous operation. A Promise object can be in one of three states:

/*a promise is constructed thus*/
const inventory = {
  sunglasses: 0,
  pants: 1088,
  bags: 1344
};


function myExecutor(resolve,reject){
  if (inventory.sunglasses >0){
    resolve('Sunglasses order processed.');
  } else{
    reject('That item is sold out.')
  }
}

function orderSunglasses(){
  return new Promise(myExecutor);
}
let orderPromise = orderSunglasses();

console.log(orderPromise);
    

Promises 2

It's perhaps more important to know how to consume promises. The following notes simulate this, with a ready made library and code to handle promises in a separate file.




/* We can also use .catch with promises*/
const {checkInventory} = require('./library.js');

const order = [['sunglasses', 1], ['bags', 2]];

const handleSuccess = (resolvedValue) => {
  console.log(resolvedValue);
};

const handleFailure = (rejectReason) => {
  console.log(rejectReason);
};

// Write your code below:

checkInventory(order).then(handleSuccess).catch(handleFailure);

Chaining Multiple Promises

Chaining promises together is called composition. Promises are designed with composition in mind



Promise.all() accepts an array of promises as its argument and returns a single promise. That single promise will settle in one of two ways:

  • If every promise in the argument array resolves, the single promise returned from Promise.all() will resolve with an array containing the resolve value from each promise in the argument array.
  • If any promise from the argument array rejects, the single promise returned from Promise.all() will immediately reject with the reason that promise rejected. This behavior is sometimes referred to as failing fast.


Async-await

Often in web development, we need to handle asynchronous actions— actions we can wait on while moving on to other tasks. We make requests to networks, databases, or any number of similar operations. JavaScript is non-blocking: instead of stopping the execution of code while it waits, JavaScript uses an event-loop which allows it to efficiently execute other tasks while it awaits the completion of these asynchronous actions.

Originally, JavaScript used callback functions to handle asynchronous actions. The problem with callbacks is that they encourage complexly nested code which quickly becomes difficult to read, debug, and scale. With ES6, JavaScript integrated native promises which allow us to write significantly more readable code. JavaScript is continually improving, and ES8 provides a new syntax for handling our asynchronous action, async...await. The async...await syntax allows us to write asynchronous code that reads similarly to traditional synchronous, imperative programs.

The async...await syntax is syntactic sugar— it doesn’t introduce new functionality into the language, but rather introduces a new syntax for using promises and generators. Both of these were already built in to the language. Despite this, async...await powerfully improves the readability and scalability of our code. Let’s learn how to use it!

/*Here are three ways of reading and printing from two files in a specified order:

    The first version uses callback functions.
    The second version uses native promise syntax
    The third version uses async...await.
    First we set up our txt files and function library
*/

/*file.txt*/
This is the first sentence.
/*file2.txt*/
This is the second sentence.

/*promisifiedReadfile.js*/
const fs = require('fs');
// Below we create a function for reading files that returns a promise. We converted the fs.readfile() function which uses callbacks. Many of the asynchronous functions you'll encounter already return promises, so this extra step is seldom necessary. 
const promisifiedReadfile = (file, encoding) => 
  new Promise((resolve, reject) => {
    fs.readFile(file, encoding, (err, text) => {
			if (err) {
				return reject(err.message);
      }
        resolve(text);
      });
});

module.exports = promisifiedReadfile

/*app.js*/
const fs = require('fs');
const promisifiedReadfile = require('./promisifiedReadfile');
      
// Here we use fs.readfile() and callback functions:
fs.readFile('./file.txt', 'utf-8', (err, data) => {
  if (err) throw err;
  let firstSentence = data;
  fs.readFile('./file2.txt',  'utf-8', (err, data) => {
    if (err) throw err;
    let secondSentence = data;
    console.log(firstSentence, secondSentence)
  });
});

// Here we use native promises with our "promisified" version of readfile:
let firstSentence
promisifiedReadfile('./file.txt', 'utf-8')
  .then((data) => {
    firstSentence = data;
    return promisifiedReadfile('./file2.txt', 'utf-8')
  })
  .then((data) => {
    let secondSentence = data;
    console.log(firstSentence, secondSentence)
  })
  .catch((err) => {console.log(err)});

// Here we use promisifiedReadfile() again but instead of using the native promise .then() syntax, we declare and invoke an async/await function:
//we wrap our asynchronous logic inside a function prepended with the async keyword. Then, we invoke that function. 
async function readFiles() {
  let firstSentence = await promisifiedReadfile('./file.txt', 'utf-8')
  let secondSentence = await promisifiedReadfile('./file2.txt', 'utf-8')
  console.log(firstSentence, secondSentence)
}
readFiles()
/*The await keyword can only be used inside an async function.
 *await is an operator: it returns the resolved value of a promise.
 *Since promises resolve in an indeterminate amount of time, await halts, or pauses, the execution of our async function
 *until a given promise is resolved.

 *In most situations, we’re dealing with promises that were returned from functions.*/
    
/*This function takes in a number. If the number is 0, it returns a promise that resolves to the string 'zero'. If the number is not 0,  it returns a promise that resolves to the string 'not zero'*/
function withConstructor(num){
  return new Promise((resolve, reject) => {
    if (num === 0){
      resolve('zero');
    } else {
      resolve('not zero');
    }
  })
}

withConstructor(0)
  .then((resolveValue) => {
  console.log(` withConstructor(0) returned a promise which resolved to: ${resolveValue}.`);
})

// async version:
async function withAsync(num){
      if (num === 0){
      return 'zero';
    } else {
      return 'not zero';
    }
};

// Leave this commented out until step 3:

withAsync(100)
  .then((resolveValue) => {
  console.log(` withAsync(100) returned a promise which resolved to: ${resolveValue}.`);
})

/*BONUS: here's codecadamy's test file for this code
 * test.js */
console.log = function () { }
const { expect } = require('chai')
const rewire = require('rewire');
const fs = require('fs');
const code = fs.readFileSync('app.js', 'utf8');

describe('', function () {
    it('', async function () {

        let appModule;
        try {
            appModule = rewire("../app.js")
        } catch (e) {
            expect(true, 'Try checking your code again. You likely have a syntax error.').to.equal(false);
        }
        let withAsync
        try {
            withAsync = appModule.__get__("withAsync");
        } catch (e) {
            expect(true, 'Did you create `withAsync`?').to.equal(false);
        }
        expect(withAsync, "`withAsync()` should be a function. Make sure to use the `async` keyword.").to.be.an.instanceOf(Function);
        let usedAsync = /async/.test(code)
        expect(usedAsync, "`withAsync()` should be an `async` function.").to.equal(true);
				let test1 = await withAsync(0);
        let test2 = await withAsync(100);
        expect(test1, "Your `async` function should return 'zero' if the argument passed to it is `0`.").to.equal('zero')
        expect(test2, "Your `async` function should return 'not zero' if the argument passed to it is not `0`.").to.equal('not zero')
    });
});

    
/*library.js
This is the shopForBeans function. It uses a setTimeout() function to simulate a time-consuming asynchronous action. The function returns a promise with a resolved value of a string representing a type of bean. It settles on a random beanType from the beanTypes array using Math.random().
*/
const shopForBeans = () => {
  return new Promise((resolve, reject) => {
	const beanTypes = ['kidney', 'fava', 'pinto', 'black', 'garbanzo'];
  setTimeout(()=>{
    let randomIndex = Math.floor(Math.random() * 5)
    let beanType = beanTypes[randomIndex];
    console.log(`I bought ${beanType} beans because they were on sale.`)
   resolve(beanType);
  }, 1000)
})
}

//other bean related functions
let soakTheBeans = (beanType) => {
   return new Promise((resolve, reject) => {
     console.log('Time to soak the beans.')
    setTimeout(()=>{
      console.log(`... The ${beanType} beans are softened.`)
      resolve(true)
      }, 1000)
  })
}

let cookTheBeans = (isSoftened) => {
  return new Promise((resolve, reject) => {
    console.log('Time to cook the beans.')
    setTimeout(()=>{
      if (isSoftened) {
        console.log('... The beans are cooked!') 
        resolve('\n\nDinner is served!')
      }
    }, 1000)
  })
}

  
module.exports = {shopForBeans, soakTheBeans, cookTheBeans} 
    
/*we can do a couple of things with this library*/

//first a fairly simple operation, getBeans
const shopForBeans = require('./library.js');

async function getBeans() {
  console.log(`1. Heading to the store to buy beans...`);
  let value = await shopForBeans();
  console.log(`3. Great! I'm making ${value} beans for dinner tonight!`);
}

getBeans();

//The true beauty of async...await is when we have a series of asynchronous actions which depend on one another. For example, we may make a network request based on a query to a database. In that case, we would need to wait to make the network request until we had the results from the database. With native promise syntax, we use a chain of .then() functions 
//using the async...await syntax can save us some typing, the length reduction isn’t the main point. Given the two versions of the function, the async...await version more closely resembles synchronous code, which helps developers maintain and debug their code. The async...await syntax also makes it easy to store and refer to resolved values from promises further back in our chain which is a much more difficult task with native promise syntax.

const {shopForBeans, soakTheBeans, cookTheBeans} = require('./library.js');

// Write your code below:
async function makeBeans(){
  let type = await shopForBeans();
  let isSoft = await soakTheBeans(type);
  let dinner = await cookTheBeans(isSoft);
  console.log(dinner);
};

makeBeans();

    
/*error handling
*library.js
With async...await, we use try...catch statements for error handling.
By using this syntax, not only are we able to handle errors in the same way we do with synchronous code
we can also catch both synchronous and asynchronous errors.
we’ve been working with a lot of promises that never reject, but this isn’t very realistic!

This time we’ve required in a function, cookBeanSouffle() which returns a promise that resolves or rejects randomly*/

//This function returns true 50% of the time.
let randomSuccess = () => {
 let num = Math.random();
 if (num < .5 ){
   return true;
 } else {
   return false;
 }
};

//This function returns a promise that resolves half of the time and rejects half of the time
let cookBeanSouffle = () => {
 return new Promise((resolve, reject) => {
   console.log('Fingers crossed... Putting the Bean Souffle in the oven');
   setTimeout(()=>{
     let success = randomSuccess();
     if(success){
       resolve('Bean Souffle');
     } else {
       reject('Dinner is ruined!');
     }
   }, 1000);
 })
};

module.exports = cookBeanSouffle;

/*app.js*/
const cookBeanSouffle = require('./library.js');

// Write your code below:


async function hostDinnerParty(){
  try{
    let dinner = await cookBeanSouffle();
    console.log( dinner + ' is served!')
  }
    catch(error){
      console.log(error);
      console.log('Ordering a pizza!');
    }
}

hostDinnerParty();



    
/*Handling Independent Promises

Remember that await halts the execution of our async function. This allows us to conveniently write synchronous-style code to handle dependent promises. But what if our async function contains multiple promises which are not dependent on the results of one another to execute? 
*library.js*/
//modified version of our cooking library
let cookBeans = () => {
  return new Promise ((resolve, reject) => {
   setTimeout(()=>{
     resolve('beans')
   }, 1000)
 })
}

let steamBroccoli = () => {
 return new Promise ((resolve, reject) => {
   setTimeout(()=>{
     resolve('broccoli')
   }, 1000)
 })
}

let cookRice = () => {
 return new Promise ((resolve, reject) => {
   setTimeout(()=>{
     resolve('rice')
   }, 1000)
 })
}

let bakeChicken = () => {
 return new Promise ((resolve, reject) => {
   setTimeout(()=>{
     resolve('chicken')
   }, 1000)
 })
}

module.exports = {cookBeans, steamBroccoli, cookRice, bakeChicken}

//app.js

let {cookBeans, steamBroccoli, cookRice, bakeChicken} = require('./library.js')

// Write your code below:

async function serveDinner(){
 const vegetablePromise = steamBroccoli();
 const starchPromise = cookRice();
 const proteinPromise = bakeChicken();
 const sidePromise = cookBeans();
 console.log(`Dinner is served. We're having ${await vegetablePromise}, ${await starchPromise}, ${await proteinPromise}, and ${await sidePromise}.`)
}

serveDinner();
    
/*Another way to take advantage of concurrency when we have multiple promises which can be executed simultaneously is to await a Promise.all().

We can pass an array of promises as the argument to Promise.all(), and it will return a single promise. This promise will resolve when all of the promises in the argument array have resolved. This promise’s resolve value will be an array containing the resolved values of each promise from the argument array. */

//app.js
let {cookBeans, steamBroccoli, cookRice, bakeChicken} = require('./library.js')

// Write your code below:

async function serveDinnerAgain(){
  let foodArray = await Promise.all([steamBroccoli(), cookRice(), bakeChicken(),cookBeans() ]);
  let vegetable = foodArray[0];
let starch =  foodArray[1];
let protein =  foodArray[2];
let side =  foodArray[3];

console.log(`Dinner is served. We're having ${vegetable}, ${starch}, ${protein}, and ${side}.`);

}
serveDinnerAgain();

    

Let’s review

  • async...await is syntactic sugar built on native JavaScript promises and generators.
  • We declare an async function with the keyword async.
  • Inside an async function we use the await operator to pause execution of our function until an asynchronous action completes and the awaited promise is no longer pending .
  • await returns the resolved value of the awaited promise.
  • We can write multiple await statements to produce code that reads like synchronous code.
  • We use try...catch statements within our async functions for error handling.
  • We should still take advantage of concurrency by writing async functions that allow asynchronous actions to happen in concurrently whenever possible.

GET Requests

The four most commonly used types of HTTP requests are GET, POST, PUT, and DELETE. In this lesson, we’ll cover GET and POST requests.Mozilla Developer Network: HTTP methods

With a GET request, we’re retrieving, or getting, information from some source (usually a website). For a POST request, we’re posting information to a source that will process the information and send it back.

Were using the Datamuse API for GET requests and the Rebrandly URL Shortener API for POST requests.

JSON Generator

Project link

The event loop

Consider the following code in the context of the event loop

console.log('First message!');
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#Zero_delays
setTimeout(() => {
   console.log('This message will always run last...');
}, 0);
console.log('Second message!');
    

XHR GET requests

/*XHR GET request boilerplate*/
const xhr = new XMLHttpRequest();

const url = 'https://api-to-call.com/endpoint';

xhr.responseType = 'json';

xhr.onreadystatechange = () => {
  if (xhr.readyState === XMLHttpRequest.DONE) {
    return xhr.response;
  }
};

xhr.open('GET', url);
xhr.send();
Project link: Wordsmith

POST Requests

/*Boilerplate AJAX POST request*/
const xhr = new XMLHttpRequest();
const url = 'https://api-to-call.com/endpoint';
const data = JSON.stringify({id: '200'});
xhr.responseType = 'json';
xhr.onreadystatechange = () =>{
  if(xhr.readyState === XMLHttpRequest.DONE){
    return xhr.response;
  }
}

xhr.open('POST', url);
xhr.send(data);
    

URL Shortener


Project link
If you want to challenge yourself:

  • Build shortenUrl() or getSuggestions() from scratch.
  • Manipulate the object that is returned to display something different in the browser.
  • Use a different API to make a GET or POST request.
  • Create query strings to yield different results.

    

Wander

Project link