Function vs. Fat Arrow

So far, you've learned two ways to write a function:

  • (...) => {...} (the "fat arrow" method): Taught in JS0
  • function(...) {...} First introduced in this chapter for writing prototype functions

What is the difference between the two function declarations?

function() {....} vs () => {....} ?

Explanation

The difference has to do with the this keyword.

Before the fat arrow

A long time ago, there were no fat arrow functions. You had to write functions like this:

const fun = function(a,b) {
...
}

What is this?

;[5, 2, 3].push(10)
// Inside the function `push`, **this** refers to whatever
// comes before the `.` ([5,2,3]) in this case.
const a = [9, 8, 7]
a.getLast()
// inside the function `getLast`, **this** refers to
// whatever comes before the `.` (a) in this case.
alert('Hi there')
// What about functions without `.`?
// What would **this** be in the function `alert`?
// Answer:
// If there is no `.`,
// the browser will automatically put `window` when it runs your code:
// window.alert("hello")
// Therefore, inside the alert function, **this** refers to the window object
// In nodejs, nodejs will automatically put `global` when it runs your code:
// global.alert("hello")
// Therefore, inside the alert function, **this** refers to the global object

What about the following example?

setTimeout(function () {
console.log(this) // What is this? Answer below
})

In functions like setTimeout that were written by someone else, you can't always know what this is, because setTimeout could be doing something like the following:

const setTimeout = function (fun) {
const newObject = {}
newObject.fun = fun // now newObject has the input function
newObject.fun()
// in the function passed into `setTimeout`,
// `this` refers to newObject
}

This makes many prototype functions really difficult to write because this will never refer to the object or array.

const arr = [1, 2, 3]
Array.prototype.delayedLast = function () {
setTimeout(function () {
console.log(this[this.length - 1])
}, 1000)
}
arr.delayedLast() // after 1000ms,
// The browser not print out what we expect,
// because `this` does not refer to the array [1,2,3].

The solution is to use a variable to store the value of this before anything else can change it, then use that variable in the argument function passed into setTimeout.

const arr = [1, 2, 3]
Array.prototype.delayedLast = function () {
const self = this
setTimeout(function () {
console.log(self[self.length - 1])
}, 1000)
}
arr.delayedLast() // after 1000ms,
// The browser will print out 3. It works!
Pop Quiz!

What happens when we run this code?

function Person(age) {
this.age = age
}
Person.complain = function () {
if (this.age >= 100)
return "I'm getting way too old for these JavaScript exercises"
return "I'm not old enough"
}
const yoda = new Person(419)
console.log(yoda.complain())
Answer

Throws an error (undefined in not a function) because the complain function is on Person, not Person.prototype.

When we try to access yoda.complain , there's no property on the yoda object called complain complain property, so it checks Person.prototype's prototype which is Object.prototype and there's no complain property on Object.prototype and there are no more prototypes to follow in the prototype chain so yoda.complain is undefined and we can't call undefined like it's a function so we get a runtime error.

We could still technically call the complain function like this to pass in yoda as the this argument: Person.complain.call(yoda) , but that would be akward. The better way to fix this is to add complain to Person.prototype like Person.prototype.complain = function() {}.

What happens when we run this code?

function Person(age) {
this.age = age
}
Person.prototype.complain = () => {
if (this.age >= 100)
return "I'm getting way too old for these JavaScript exercises"
return "I'm not old enough"
}
const yoda = new Person(419)
console.log(yoda.complain())
Answer

Silent logic error. When we define Person.prototype.complain, we set it as an arrow function. Arrow functions bind the current this value in scope to that arrow function so it's functionally similar to defining a function and binding it, so this () => { ... } is functionally equivalent to this (function() { ... }).bind(this).

Using an arrow function and binding the this value is a problem because later when we try to pass in yoda as the this argument by passing yoda to the left of the dot in yoda.complain(), the this value passed in will be the value that was bound when the function was defined. Since we're in the global scope, the this value is window or global.

So we'll get a silent logic error because we'll check this.age, which is window.age or global.age which is undefined (unless there's a global variable with the name age) and undefined >= 100 is false. So the complain function will always return "I'm not old enough" even if the person is really old. The way to fix this is by defining Person.prototype.complain as an unbound non-arrow function like this: Person.prototype.complain = function().

The Fat Arrow () => {} was designed to solve the above! Using a fat arrow function, you can write functions that use this to refer to the affected object without creating a new variable.

const arr = [1, 2, 3]
Array.prototype.delayedLast = function () {
setTimeout(() => {
console.log(this[this.length - 1])
}, 1000)
}
arr.delayedLast() // After 1000ms,
// the browser will print out 3. It works!
On semicolons. Some developers always use it, some don't. Choice is yours.

Some JavaScript engineers would tell their engineers to always use semicolons at the end of each statement. Otherwise, the code may all get mixed together. If we rewrite the above example with the following code, it will not run

Array.prototype.delayedLast = function () {
setTimeout(() => {
console.log(this[this.length - 1])
}, 1000)
}[(1, 2, 3)].delayedLast()

This is because the computer reads the code like this: ...}[1,3,3].delayedLast(), which means something else (The author himself doesn't know what it means, so this level of detail is probably not important to learn right now).

The solution is to put ; between the previous code and the array to separate it.

Array.prototype.delayedLast = function () {
setTimeout(() => {
console.log(this[this.length - 1])
}, 1000)
}
;[1, 2, 3].delayedLast()

The Fat Arrow is a lighter way to write functions because unlike the old way of writing function, fat arrow functions do not create a this variable. Looking at the setTimeout code above:

...
setTimeout( () => {
console.log(this[this.length-1])
}, 1000)
...

The function argument that we pass into setTimeout is a fat arrow function and therefore does not have its own this variable. As a result, when the argument function runs, this simply refers to the this value in the outer function, which is the object.

It is common for software engineers to use fat arrows when writing functions () => {...}. If you need to use this in your function, like in the delayedLast function above, you must write functions in the old fashioned way: function () {...} This way you can use this to refer to the array to the left of delayedLast. Any additional functions that you write inside (such as the function argument to setTimeout) should be fat arrow functions so they have access to the this variable.

Import / Export

Imagine you have created a bunch of awesome functions that you want to use in other files. To do this, you simply give the exports key of the module object a value. Whatever value you set will be exported. module.exports is a keyword provided by nodeJS. You've seen it used to export solution files to a test file.

Some modules are dropping support for commonJS. You may have to use import instead of require and write "type": "module" into your package.json file. Read more ESM modules

module.exports = [1, 2, 3] // What is module.exports?
// What type of data is module?
Answer
module.exports = [1, 2, 3] // module.exports is an array: [1,2,3]
// module is obviously an object because it has a key called exports.

Examples

  1. Export: Any file that exports something can be called a library. Here we'll create two simple libraries.

    1. Create a file called helper1.js and write in the code below.

      ./helper1.js - exports an object with 2 keys: data (object) and getData (function)

      const info = {
      ironman: 'arrogant',
      spiderman: 'naive',
      hulk: 'strong'
      }
      module.exports = {
      data: info,
      getData: key => {
      return info[key]
      }
      }
    2. Create a file called helper2.js and write in the code below.

      ./helper2.js - exports an array

      module.exports = ['ironman', 'strange', 'thor', 'spiderman', 'hulk']
  2. Import: We will create a file that imports the libraries created in the steps above.

    1. Create a file called solution.js and write in the code below.

      ./solution.js - node will be running this file. helper1.js and helper2.js are files from the section above.

      // myObj takes the value of module.exports, which is an object
      const myObj = require('./helper1.js')
      // myArr takes the value of module.exports, which is an array
      const myArr = require('./helper2.js')
      const result = myArr.filter(e => {
      return myObj.getData(e)
      }) // what is result?
      console.log(result)
Answer
// myObj takes the value of module.exports, which is an object
const myObj = require('./helper1.js')
// myArr is ["ironman", "strange", "thor", "spiderman", "hulk"]
const myArr = require('./helper2.js')
const result = myArr.filter(e => {
return myObj.getData(e)
})
// result is ["ironman", "spiderman", "hulk"]
/*
When filter runs....
e is "ironman", myObj.getData("ironman") returns "arrogant" (truthy)
e is "strange", myObj.getData("strange") returns undefined (falsey)
e is "thor", myObj.getData("thor") returns undefined (falsey)
e is "spiderman", myObj.getData("spiderman") returns "naive" (truthy)
e is "hulk", myObj.getData("hulk") returns "strong" (truthy)
filter returns ["ironman", "spiderman", "hulk"]
*/

Exercises

  1. Create 2 files, ./myObj.js, ./myFun.js, that export an object and a function respectively.
Answer
// myObj.js
module.exports = {
name: 'Butterfree',
level: 11
}
// myFun.js
module.exports = () => {
return 500
}
  1. Create 1 file that imports the files created above and does something with the imported object and function. Make sure to console.log your output to make sure your functions are called correctly.
Answer
// new file (any name, same folder as myObj.js and myFun.js)
const pokemon = require('./myObj')
const fun = require('./myFun')
const sum = fun() + pokemon.level
console.log(sum)

Libraries

Now that you know how to use require, let's try to require a library that other people have written. A simple library that does not require any download is fs, a library that gives you functions to interact with the files and folders on your computer.

const fs = require('fs')
fs.readdir('./', (err, files) => {
console.log(files) // files should be an array
})

Exercise: Edit the code so you only console.log filenames with length < 10.

Answer
const fs = require('fs')
fs.readdir('./', (err, files) => {
files.forEach(fileName => {
if (fileName.length < 10) {
console.log(fileName)
}
})
})

fs.readdir takes in 2 arguments, a string and a function.

Why do you need to pass in a function?

Just like how it takes you time to read a book, fs.readdir needs time to look through the entire folder. When it is done looking through the folder, it will run your function and pass in 2 arguments: error (if there are issues, null otherwise), and an array of filenames in the folder.

fs.readdir('./', (err, files) => {
console.log('a')
})
console.log('b')
// What gets printed out?
Answer
// "b"
// "a"
// This is because it takes time for fs.readddir to read the folder.

Practical Applications

Now that you know how to import files, you should try to import some functions that other people have written!

  1. Write a function called makeFiles that takes in a number (X) and creates X+1 files using fs.writeFile.

    • The filenames should look like: trainer0.txt, trainer1.txt, trainer2.txt, ... trainerX.txt.

    • When you open the file, each file should have the contents: Gotta catch 'em all

    • fs.writeFile takes in 3 arguments:

      • string (filename)
      • string (file content)
      • function (to run when the function has finished creating the file; in case you want to check for errors or inform the user that the write succeeded)

      Here's an example of how fs.writeFile works:

      fs.writeFile('./today.txt', 'today is a beautiful day', () => {})
      // This will create a file called today.txt in the same folder
      // When you open the file, it will say 'today is a beautiful day'
Tests
const fs = require('fs')
describe('makeFiles function', () => {
fn.makeFiles(2)
const files = fs.readdirSync('./')
it('should create 3 files', () => {
const foundAll =
files.find(e => {
return e === 'trainer0.txt'
}) &&
files.find(e => {
return e === 'trainer1.txt'
}) &&
files.find(e => {
return e === 'trainer2.txt'
})
expect(foundAll).toBeTruthy()
})
it('should put "Gotta catch \'em all" in the files', async () => {
await fs.readFile('./trainer1.txt', { encoding: 'utf8' }, (_err, data) => {
expect(data).toEqual("Gotta catch 'em all")
})
})
})
Answer
const fs = require('fs')
const makeFiles = (n, i = 0) => {
if (i > n) {
return
}
fs.writeFile(`./trainer${i}.txt`, "Gotta catch 'em all", () => {})
return makeFiles(n, i + 1)
}
  1. Write a function called listFiles that reads the current folder and creates a file called files.html that looks like the following:

    <h1>file1</h1><h1>file2</h1>...<h1>fileX</h1>

    We won't be able to test this one since we don't know all the files in your folder.

Answer
const fs = require('fs')
const listFiles = () => {
fs.readdir('./', (err, files) => {
const str = files.reduce((acc, f) => {
return `${acc}<h1>${f}</h1>`
}, '')
fs.writeFile('./files.html', str, () => {})
})
}
Debrief

You might have tried putting all the <h1>s into an array and then writing it. Unfortunately, fs.writeFile writes arrays with commas separating the elements, so reduce is a better choice here.

APIs

An API (application programming interface) is an interface that other engineers set up for you to interact with. For example:

  • When you send a request to Netflix's API, you get a list of movies in Netflix's catalog, top-rated movies on Netflix, etc.
  • When you send a request to the Google Contacts API, you get a list of your contacts or your friend's phone number.
  • When you send a request to Twilio's API with a string and people's phone numbers, it will help you send the string as a text message to each of the phone numbers.

... and many, many more.

What is an example of something you can build with the APIs mentioned above?

You can write a file that sends a text message to each of your friends every week with the top-rated movies on Netflix!

// This code does not work.
// It is meant to give you an idea of what the code
// would look like.
// Take the time to read and understand the code.
const request = require('request')
const txtTopRatedMoviesTo = people => {
request('netflix.com/api/topCatalog', movies => {
people.forEach(number => {
// A Twilio token is needed so twilio knows it's me
// and charges me for every message I send
request('twilio.com/api/sendMessage', { token: twilio_token, to: number })
})
})
}
const runApp = () => {
// A Google token is needed so Google knows it's me
request('google.com/contacts', { token: google_token }, contacts => {
txtTopRatedMoviesTo(contacts)
})
setTimeout(runApp, 7 * 24 * 60 * 60 * 1000)
}
runApp()
Callback HELL

The above code is good, readable code with good function names.

The below example, with multiple nested request function calls, is a form of bad code called callback hell. We'll learn a little more about this later.

const request = require('request')
const runApp = () => {
// A Google token is needed so Google knows it's me
request('google.com/contacts', { token: google_token }, contacts => {
request('netflix.com/api/topCatalog', movies => {
people.forEach(number => {
// A Twilio token is needed so Twilio knows it's me
// and charges me for every message I send
request('twilio.com/api/sendMessage', {
token: twilio_token,
to: number
})
})
})
})
setTimeout(runApp, 7 * 24 * 60 * 60 * 1000)
}
runApp()

Request

There are many libraries we can use to send a request, and the request library is one of them. You might have noticed it in the above examples. You can use this library to send requests to APIs and even to retrieve web pages like your browser does.

To run the below code, you'll need to install request by running npm install request.

const request = require('request')
// We are sending a request to a Pokemon API to get Pokemon data.
// This one actually works!
request(
'https://pokeapi.co/api/v2/pokemon/?offset=20&limit=20',
(err, res, data) => {
console.log(data) // data is JSON
// We use JSON.parse to parse the JSON back into an object
const pokeInfo = JSON.parse(data)
console.log(pokeInfo.results) // Array of pokemon!
}
)

URLs

A typical URL looks like the above.

  • Protocol: Specifies how data is sent over the Internet
  • Hostname: Helps identify where to send a request to
  • Path & Query Parameters: Helps the server determine what to do with the request
    • Query parameters should be used for data. & separates the different pieces of data.
      • https://macys.com/shoes?size=4&brand=allbirds&type=outdoors

When you type a URL into your browser, the browser sends a request just like the Pokemon example above. In fact, if you send a request to any website, such as https://news.ycombinator.com, you will see the same HTML response that the browser gets back when it sends the request. The browser then reads the HTML and follows the instructions defined by each tag.

const request = require('request')
request('https://news.ycombinator.com', (err, res, data) => {
console.log(data) // data is a string of HTML tags
})

A note about APIs Different APIs/companies will respond with data in different formats. Register.gov, Facebook.com, google.com, and c0d3.com all have different data structures. They may respond back with an array of objects, an object of objects, an array of strings, etc. So you gotta try out each API before coding your project! Usually software engineers try out APIs by using PostMan or by writing requests into a JavaScript file and running the file with node. Due to possible security implications, software engineers normally will not try out APIs in the browser.

Exercises

  1. Send a request to [https://www.c0d3.com/api/lessons](https://www.c0d3.com/api/lessons) and console.log all the titles.
Hint

Don’t forget that if you want to do anything interesting with the results of a request you’ll usually have to JSON.parse it.

Answer

First, open the url in your browser to understand the data. If the data looks messy, installing a browser extension like JSON View would help. Looking at the data, we will have 10 titles, so if our function worked out console.log should be called 10 times.

  1. Tests

    We are redefining console.log to overwrite its original functionality (Mocking)

    Since there are 10 lessons, after we run getLessons, our custom console.log should have run 10 times.

    Therefore, we keep a variable i to keep track of how many times the function has been called.

    getLesson takes time to run, so we will give it 2 seconds to run before expecting i value to be 10.

    Mocking

    jest.mock('request')
    const request = require('request')
    const lessons = require('./printLessonTitles')
    describe('lessons', () => {
    test(`console log should not be called if lessons `, () => {
    request.mockClear()
    lessons.printLessons()
    expect(request.mock.calls.length).toEqual(1)
    const firstCall = request.mock.calls[0]
    expect(firstCall[0]).toEqual('https://c0d3.com/api/lessons')
    })
    test('console.log should be called once if length of lessons array is 1', () => {
    request.mockClear()
    lessons.printLessons()
    console.log = jest.fn()
    request.mock.calls[0][1](
    {},
    {},
    JSON.stringify([
    {
    title: 'testing'
    }
    ])
    )
    expect(request.mock.calls.length).toEqual(1)
    expect(console.log.mock.calls[0][0]).toEqual('testing')
    })
    test('console.log should return 3 times if lessons array has 3 elements', () => {
    request.mockClear()
    lessons.printLessons()
    console.log = jest.fn()
    request.mock.calls[0][1](
    {},
    {},
    JSON.stringify([
    {
    title: 'Testing1'
    },
    {
    title: 'Testing2'
    },
    {
    title: 'Testing3'
    }
    ])
    )
    expect(console.log.mock.calls.length).toEqual(3)
    expect(console.log.mock.calls[0][0]).toEqual('Testing1')
    expect(console.log.mock.calls[1][0]).toEqual('Testing2')
    expect(console.log.mock.calls[2][0]).toEqual('Testing3')
    })
    })
  2. Shape

    module.exports = {
    printLessons: () => {
    // your code here...
    }
    }
  3. Explanation

    • Require request.
    • Send the request to https://c0d3.com/api/lessons.
      • After we get the data, we need to convert the data into an array using JSON.parse
      • run console.log for each of the title in the array.
  4. Code

    const request = require('request')
    request('https://c0d3.com/api/lessons', (err, res, body) => {
    const parsedJson = JSON.parse(body)
    parsedJson.forEach(thisLesson => {
    console.log(thisLesson.title)
    })
    })
  1. Send a request to [https://www.c0d3.com/api/lessons](https://www.c0d3.com/api/lessons) and write all the titles into a file called lessons.html with the following content:

    <h1>title1</h1><h1>title2</h1>...<h1>titleX</h1>
Hint

Asynchronous behavior makes it tricky to split your code into functions, say by getting the lesson titles in one function and writing them into a file in another, because the request doesn't return its results. As you get better with promises, you'll learn how to easily split these tasks up, but for now you can plan on putting everything that depends on the results inside the request.

Answer
  1. Test

    jest.mock('request')
    const request = require('request')
    const fs = require('fs')
    const titlesDoc = require('./lessonTitles')
    describe('Titles Document', () => {
    test('should write two titles', () => {
    request.mockClear()
    titlesDoc.createTitlesDoc()
    fs.writeFile = jest.fn()
    request.mock.calls[0][1](
    {},
    {},
    JSON.stringify([
    {
    title: 'c0d3'
    },
    {
    title: 'recursion'
    }
    ])
    )
    expect(fs.writeFile.mock.calls.length).toEqual(1)
    expect(fs.writeFile.mock.calls[0][1]).toEqual(
    '<h1>c0d3</h1><h1>recursion</h1>'
    )
    })
    })
  2. Shape

    module.exports = {
    createTitlesDoc: () => {
    // your code here...
    }
    }
  3. Explanation

    • Require request and fs.
    • Send the request to https://c0d3.com/api/lessons.
      • After we get the data, we need to convert the data into an array using JSON.parse .
      • Use the returned data to find your lessons title and wrap it with h1 tag.
      • Create a file lessons.html.
  4. Code

    //lessonTitles.js
    const request = require('request')
    const fs = require('fs')
    module.exports = {
    createTitlesDoc: () => {
    request('https://c0d3.com/api/lessons', (err, res, body) => {
    const parsedJson = JSON.parse(body)
    const titleArray = parsedJson.map(thisLesson => {
    return thisLesson.title
    })
    const titles = titleArray.reduce((acc, thisTitle) => {
    return `${acc}<h1>${thisTitle}</h1>`
    }, '')
    fs.writeFile('lessons.html', titles, () => {})
    })
    }
    }

Did you notice how you had to examine the output from each API to see how to access the results you needed? It’s easy to do it in node in the terminal; just request(“<API ADDRESS HERE>”, (err, res, data) => { console.log(data) }).

  1. Send a request to https://pokeapi.co/api/v2/pokemon/ and write all the names into a file called names.html with the following content:

    <h1>name1</h1><h1>name2</h1>...<h1>nameX</h1>
Answer
  1. Test

    jest.mock('request')
    const request = require('request')
    const fs = require('fs')
    const pokemon = require('./getPokemonNames')
    describe('Pokemons', () => {
    test('should write two different pokemon names', () => {
    request.mockClear()
    pokemon.getNames()
    fs.writeFile = jest.fn()
    request.mock.calls[0][1](
    {},
    {},
    JSON.stringify({
    results: [
    {
    name: 'RahiZzYyY'
    },
    {
    name: 'McGiggles'
    },
    {
    name: 'BrownDynamite'
    }
    ]
    })
    )
    expect(fs.writeFile.mock.calls.length).toEqual(1)
    expect(fs.writeFile.mock.calls[0][1]).toEqual(
    '<h1>RahiZzYyY</h1><h1>McGiggles</h1><h1>BrownDynamite</h1>'
    )
    })
    })
  2. Shape

    module.exports = {
    getNames: () => {
    // your code here...
    }
    }
  3. Explanation

    • Require request and fs.
    • Send a request to https://pokeapi.co/api/v2/pokemon.
      • After we get the data, we need to convert the data into an array using JSON.parse .
      • Create an array of pokemon names from the data received and attach h1 tag to each pokemon name.
      • Create a file names.html.
  4. Code

//getPokemonNames.js
const request = require('request')
const fs = require('fs')
request('https://pokeapi.co/api/v2/pokemon/', (err, res, body) => {
const parsedJson = JSON.parse(body)
const names = parsedJson.results.reduce((acc, pokemon) => {
return `${acc}<h1>${pokemon.name}</h1>`
}, '')
fs.writeFile('names.html', names, () => {})
})

You’ll notice that we’re accessing parsedJson.results ; it’s common for APIs to return one big object that includes both metadata (which page we’re on, how many results were found, what website this is) and results (as an array).

  1. Send a request to https://api.openaq.org/v1/countries to get all countries, and console.log the country with the largest number of cities.
Hint

Remember our trick to get reduce to return 2 pieces of information? Here you can use it to keep track of both the name of the winning country and the number of cities it has.

Answer
  1. Test

    jest.mock('request')
    const request = require('request')
    const getCountry = require('./getCountryWithMostCities')
    describe('Countries', () => {
    test('find the country with most cities', () => {
    request.mockClear()
    getCountry.getMostCities()
    console.log = jest.fn()
    request.mock.calls[0][1](
    {},
    {},
    JSON.stringify({
    results: [
    {
    name: 'Narnia',
    cities: 100
    },
    {
    name: 'SpaceJam',
    cities: 48
    },
    {
    name: 'Pluto',
    cities: 250
    },
    {
    name: 'Galaxy',
    cities: 20
    }
    ]
    })
    )
    expect(console.log.mock.calls[0][0]).toEqual('Pluto')
    })
    })
  2. Shape

    module.exports = {
    getMostCities: () => {
    // your code here...
    }
    }
  3. Explanation

    • Require request
    • Send a request to https://api.openaq.org/v1/countries.
      • After we get the data, we need to convert the data into an array using JSON.parse.
      • Find the country with the most cities and console.log it.
  4. Code

    const request = require('request')
    request('https://api.openaq.org/v1/countries', (err, res, body) => {
    const countries = JSON.parse(body)
    const mostCities = countries.results.reduce((acc, elem) => {
    if (!acc.name || elem.cities > acc.cities)
    return { name: elem.name, cities: elem.cities }
    return acc
    }, {})
    console.log(mostCities.name)
    })
  1. Send a request to https://pokeapi.co/api/v2/pokemon/ and console.log the Pokemon that weighs the most out of the first 20 Pokemon (if you don't give it a limit, the Pokemon API will default to 20 Pokemon). Look at the response—you will notice that each Pokemon has a URL. You need to send another request for each Pokemon to get its weight.
Answer
  1. Test

    jest.mock('request')
    const request = require('request')
    const heaviest = require('./getHeaviestPokemon')
    describe('Pokemons', () => {
    test('console.log the heaviest pokemon', () => {
    request.mockClear()
    heaviest.heaviestPokemon()
    console.log = jest.fn()
    request.mock.calls[0][1](
    {},
    {},
    JSON.stringify({
    results: [
    {
    name: 'Rocky',
    url: 'testing1'
    },
    {
    name: 'Zoolander',
    url: 'testing2'
    },
    {
    name: 'Naruto',
    url: 'testing'
    }
    ]
    })
    )
    expect(request.mock.calls.length).toEqual(4)
    request.mock.calls[1][1]({}, {}, JSON.stringify({ weight: 200 }))
    request.mock.calls[2][1]({}, {}, JSON.stringify({ weight: 300 }))
    request.mock.calls[3][1]({}, {}, JSON.stringify({ weight: 100 }))
    expect(console.log.mock.calls[0][0]).toEqual(
    'Heaviest Pokemon is Zoolander at 300 pounds'
    )
    })
    })
  2. Shape

    module.exports = {
    getHeaviest: () => {}
    }
  3. Explanation

    • Require request.
    • Send request to https://pokeapi.co/api/v2/pokemon/
      • After we get the data, we need to convert the data into an array using JSON.parse .
      • Send request to each pokemon's url to find the weight and return the heaviest pokemon.
  4. Code

    const request = require('request')
    request('https://pokeapi.co/api/v2/pokemon/', (err, res, body) => {
    const parsedJson = JSON.parse(body)
    const pokemonList = []
    parsedJson.results.forEach(thisPokemon => {
    const name = thisPokemon.name
    request(thisPokemon.url, (err, pokeRes, pokeBody) => {
    const data = JSON.parse(pokeBody)
    const weight = data.weight
    pokemonList.push({
    name: name,
    weight: weight
    }) // Shorter syntax: {name, weight}
    if (parsedJson.results.length === pokemonList.length) {
    const heaviest = pokemonList.reduce((acc, poke) => {
    if (poke.weight >= acc.weight) {
    return poke
    }
    return acc
    }, pokemonList[0])
    console.log(
    `Heaviest Pokemon is ${heaviest.name} at ${heaviest.weight} pounds`
    )
    }
    })
    })
    })
Debrief

That was a long function, wasn't it? In the next section, you'll see how to make multiple request calls much cleaner and easier to read. For now, let's look at how we went about coding this:

Examples: The function should only do one thing—examine 20 Pokemon and find the heaviest, then console.log it.

Function shape: We know the main function will be a request. We'll send 20 additional requests to find the weight of each Pokemon, but because they'll need the URLs returned by the first request, they'll have to be inside the first request's callback function.

Only after all 20 weights have been found can we use something like reduce to compare them and find the heaviest. That's tricky because we don't know in which order they'll come back! So each of the 20 callback functions will have to check if it's the last, and if so, complete the program by comparing the Pokemon and logging the heaviest.

request('https://pokeapi.co/api/v2/pokemon/', (err, res, body) => {
// parse the JSON body
parsedJson.results.forEach((el) => {
request(/*URL for this Pokemon*/, (err, pokeRes, pokeBody) => {
// store this Pokemon's data for later
// if all 20 weights have been found:
// compare the Pokemon
// log the heaviest one
})
})
})

Think: We'll need to start an array to keep track of the names and weights once they've been found outside the forEach so that each request can add to it. Once this array has 20 elements (the same number as the original results list) we're ready to compare them, which we'll do with reduce.

Code: Here we create our const pokemonList = [] array, parse the JSON each time it comes in, push each newly found weight to pokemonList, and write our if condition and reduce call. We're done!

Test: Running the code as-is should always log Venusaur. If you want to test it on a bigger data set, see the next exercise for how to grab more than 20 Pokemon from the API. For example, if you change the limit to 100, the answer should become golem!

  1. Send a request to https://pokeapi.co/api/v2/pokemon/ and then send a request to get information for each Pokemon to get its image (sprites.front_default). Create an HTML page with 100 Pokemons' names and images.

    (To get 100 Pokemon instead of 20, just pass a parameter into the URL: https://pokeapi.co/api/v2/pokemon?limit=100.)

    The HTML img tag is a single tag that takes the URL of an image in its src (source) attribute.

    <div><p>name1</p><img src="image1" /></div>...<div><p>namex</p><img src="imagex" /></div>
Answer
  1. Tests

    jest.mock('request')
    const request = require('request')
    const fs = require('fs')
    const pokemons = require('./createPokemonProfile')
    describe('Pokemons', () => {
    test('return 3 pokemon profiles', () => {
    request.mockClear()
    pokemons.createProfile()
    fs.writeFile = jest.fn()
    request.mock.calls[0][1](
    {},
    {},
    JSON.stringify({
    results: [
    {
    name: 'jollyRancher',
    url: 'testing1'
    },
    {
    name: 'johnnyBravo',
    url: 'tesing2'
    },
    {
    name: 'blueEyeDragon',
    url: 'testing3'
    }
    ]
    })
    )
    expect(request.mock.calls.length).toEqual(4)
    request.mock.calls[1][1](
    {},
    {},
    JSON.stringify({
    sprites: {
    front_default: 'testingPicture1'
    }
    })
    )
    request.mock.calls[2][1](
    {},
    {},
    JSON.stringify({
    sprites: {
    front_default: 'testingPicture2'
    }
    })
    )
    request.mock.calls[3][1](
    {},
    {},
    JSON.stringify({
    sprites: {
    front_default: 'testingPicture3'
    }
    })
    )
    expect(fs.writeFile.call.length).toEqual(1)
    expect(fs.writeFile.mock.calls[0][1]).toEqual(
    '<div><p>jollyRancher</p><img src="testingPicture1"/></div><div><p>johnnyBravo</p><img src="testingPicture2"/></div><div><p>blueEyeDragon</p><img src="testingPicture3"/></div>'
    )
    })
    })
  2. Shape

    module.exports = {
    createProfile: () => {
    // your code here...
    }
    }
  3. Explanation

    • Require request and fs.
    • Send a request to https://pokeapi.co/api/v2/pokemon?limit=100.
      • After we get the data, we need to convert the data into an array using JSON.parse.
      • Create an array objects with pokemon's name and image.
      • Attach the p and div tags to the pokemon's name.
      • Create a file namesandimages.html
  4. Code

    const request = require('request')
    const fs = require('fs')
    request('https://pokeapi.co/api/v2/pokemon?limit=100', (err, res, body) => {
    const parsedJson = JSON.parse(body)
    const pokemonList = []
    parsedJson.results.forEach(thisPokemon => {
    const pokemonName = thisPokemon.name
    request(thisPokemon.url, (err, pokeRes, pokeBody) => {
    const data = JSON.parse(pokeBody)
    pokemonList.push({
    name: pokemonName,
    pic: data.sprites.front_default
    })
    if (pokemonList.length === parsedJson.results.length) {
    const htmlStr = pokemonList.reduce((acc, f) => {
    return `${acc}<div><p>${f.name}</p><img src="${f.pic}"/></div>`
    }, '')
    fs.writeFile('namesandimages.html', htmlStr, () => {
    ;``
    })
    }
    })
    })
    })

Promises

If you are not careful when using the request library, you may end up in callback hell.

Code that resembles callback hell is highly discouraged because it can get really confusing and difficult for other engineers to understand. In fact, our code in the last couple of exercises was starting to get a little complex with nested request calls.

If you submit code that looks like callback hell it will most likely be rejected.

request('https://a.com', (aErr, aRes, aData) => {
request('https://b.com', (bErr, bRes, bData) => {
request('https://c.com', (cErr, cRes, cData) => {
request('https://d.com', (dErr, dRes, dData) => {
request('https://e.com', (eErr, eRes, eData) => {
// In a large codebase, it may be very difficult
// to get to the end of the chain (eData)
// since the code is in the middle of the page
calculateResult(aData, bData, cData, dData, eData)
})
})
})
})
}) // Bad coding pattern
How an experienced engineer might write code that does not end up in callback hell
const onReceiveResponseA = (aErr, aRes, aData) => {
// do things with aData
request('https://b.com', onReceiveResponseB)
}
const onReceiveResponseB = (bErr, bRes, bData) => {
// do things with bData
request('https://c.com', onReceiveResponseC)
}
const onReceiveResponseC = (cErr, cRes, cData) => {
// do things with cData
request('https://d.com', onReceiveResponseD)
}
const onReceiveResponseD = (dErr, dRes, dData) => {
// do things with dData
request('https://e.com', onReceiveResponseE)
}
const onReceiveResponseE = (eErr, eRes, eData) => {
calculateResult(aData, bData, cData, dData, eData)
}
request('https://a.com', onReceiveResponseA)

To help junior engineers avoid making mistakes like the above, functions can return an object called a promise. A promise represents the eventual result of an asynchronous action (e.g. making a network request, writing files to the filesystem).

The two most commonly used promise methods are:

  1. then
    • then expects a function argument, which will be called when the promise is done.
    • When the promise is eventually fulfilled, then receives the resulting value and calls the function, passing it this value as an argument.
    • The function can return any value (numbers, strings, or any other data).
    • Finally, then returns another promise with the value returned by the function.
  2. catch
    • catch expects a function argument, which will be called when the promise encounters an error.
    • When the promise encounters an error, catch receives an error object and calls the function, passing it this error as an argument

Axios

axios is a library that also sends requests, but unlike request, where you pass in a function as the second argument, axios returns a promise, ****and you pass the next function into the promise's then function. This makes it easy to organize a chain of functions that depend on each other's results, listing them top-down instead of nesting them and creating callback hell.

Instead of passing a function as a second argument, you pass the function as an argument into the return promise's then function.

Differences
// You need to install axios
// because it is not a nodejs core (libraries that come installed with nodejs)
// To install, run `npm install axios` before running this file.
const axios = require('axios')
const allData = []
const resultOfDataPromise = axios('https://a.com')
.then(aData => {
allData.push(aData)
return axios('https://b.com')
})
.then(bData => {
allData.push(bData)
return axios('https://c.com')
})
.then(cData => {
allData.push(cData)
return axios('https://d.com')
})
.then(dData => {
allData.push(dData)
return axios('https://e.com')
})
.then(eData => {
allData.push(eData)
// In a large codebase, using returning promise objects can help your
// code flow in a logical way (top down) so the last call in the
// chain (eData) is towards the end of the page.
return calculateResult(allData)
})
.catch(eErr => {
// Find the eErr variable in the callback hell example
// and try to understand how this works!
})

Another benefit is that a promise's .then function always returns another promise, allowing you to chain them. Continuing from the example above, you can chain from the computed result:

const userNamesPromise = resultOfDataPromise.then(result => {
// result is the return value from calling
// calculateResult(allData) above.
return getUserNames(result)
})
// To use the final usernames result (i.e. to print it out)
userNamesPromise.then(userNames => {
console.log(userNames)
})

Fetch

The browser provides you with a function called fetch that allows you to send requests and returns a promise. Unlike axios, you must do an extra step of parsing the JSON response into an object.

node-fetch is a not a nodeJS core library (meaning it did not come bundled when you installed NodeJS), so you need to install the library before using it by running npm install node-fetch.

Example

Here's an example of how fetch might be used in a web environment. We are sending a request to [c0d3.com](http://c0d3.com) and displaying how many lessons there are.

Note that when using fetch in the browser we don't need to require it; the browser supports fetch automatically. Later examples will include require('node-fetch') in case you want to follow along in node.

<h1 class="display"></h1>
<script>
const display = document.querySelector('.display')
fetch('https://www.c0d3.com/api/lessons').then( (res) => {
return res.json()
}).then( (data) => {
display.innerText = `there are ${data.length} lessons`
})
</script>

Notice how we create two promises here: one to get the data using json() and another to display it. json() is different from JSON.parse(); instead of a helper function to be used on any text, it's a function of the fetch library, and runs asynchronously, which is why it needs its own promise.

Exercise

Complete the below all the way up to e.com so that it has the same result as the axios example!

const fetch = require('node-fetch')
const allData = []
const resultOfDataPromise = fetch('https://a.com').then( (r) => {
// json is a function that turns the response string into a JavaScript data type
return r.json()
}).then( (aData) => {
allData.push(aData)
return fetch('https://b.com')
}).then(
Answer
const fetch = require('node-fetch')
const allData = []
const resultOfDataPromise = fetch('https://a.com')
.then(r => {
return r.json()
})
.then(aData => {
allData.push(aData)
return fetch('https://b.com')
})
.then(bData => {
return bData.json()
})
.then(bData => {
allData.push(bData)
return fetch('https://c.com')
})
.then(cData => {
return cData.json()
})
.then(cData => {
allData.push(cData)
return fetch('https://d.com')
})
.then(dData => {
return dData.json()
})
.then(dData => {
allData.push(dData)
return fetch('https://e.com')
})
.then(eData => {
return eData.json()
})
.then(eData => {
allData.push(eData)
return calculateResult(allData)
})

Because the browser only supports fetch, software engineers prefer to use the node-fetch library instead of axios when writing code to run on the computer. It does not matter whether you use axios or node-fetch to send requests from your computer. The choice is yours.

AJAX: Sending a request from the browser

  • If someone tells you they know ajax, it means that they know how to send requests from the browser.
  • If you are able to run the examples above on the browser, it means that you know how to use fetch to send a request from the browser. So you can now claim that you know ajax too!

Promise.all

Many times you may want to send more than one request at once. For example, if you want to look up the prices of 20 houses in your neighborhood (or the weights of 20 Pokemon), you want to send all the requests to get the data at once, then do something with all the data (such as finding the cheapest house or the heaviest Pokemon).

To do this, you can use Promise.all, which takes in an array of promise objects and returns a promise. When the then function runs, the argument function will get an array of responses.

const fetch = require('node-fetch')
const pokeNumbers = [
37, // vulpix
38, // ninetales
39, // jigglypuff
40 // wigglytuff
]
const arrayPromises = pokeNumbers.map(num => {
return fetch(`https://pokeapi.co/api/v2/pokemon/${num}`).then(result => {
// result is an array of response objects, one for each request
// we need to parse the JSON in each result
return result.json()
})
})
Promise.all(arrayPromises).then(results => {
// results is now an array of objects
// we can do something with it, like
results.forEach(e => {
console.log(`${e.name} weighs ${e.weight}`)
})
})
(Throwback) Teaching Callback Hell in 2017

This section should be updated before the lesson goes live

Student Questions / Rough Explanations

Here are some questions students asked about Promises that students have asked.

The responses are raw, in chat format.

  • promise !== data

    • That's why I couldn't see the data when I console.log() promises, the computer said just [promise]
  • I'm learning promises, I don't understand why that json() runs asynchronously means that it needs its own promise.

  • Also, I don't know why fetch is useful in this example. I assume that if it was axios, we don't have to parse it and the code is shorter.

    • Because browsers only support fetch, No axios in the front end (Unless you bring in a new library)
  • I thought that... using .json() instead of JSON.parse() is necessary because .json() makes sure that the computer operates the functions in order

    • response comes in patches. Sometimes the data a BIG right?

      so as data response comes back

      it's converting into json

      JSON.parse( string ) -> the string has to exist completely first

      but with response over the internet, this is a little tricky

      because.... responses comes in backets

      like... you ask your mom for 100k.... your mom sends you 1k every month

      like.... the internet works that way

      does that make more sense?

      so.... res.json() -> returns a promise. It takes the responses and as the packets come in, it puts them together as an object. then resolve when everything has been received

      that's what happens in the background.

Exercises

  1. Modify your solution to exercise 2 from the previous section to use axios or node-fetch: Send a request to [https://www.c0d3.com/api/lessons](https://www.c0d3.com/api/lessons) and write all the titles into a file called lessons.html with the following content:

    <h1>title1</h1><h1>title2</h1>...<h1>titleX</h1>
Answer
  1. Test

    jest.mock('node-fetch')
    jest.mock('fs')
    const fetch = require('node-fetch')
    const fs = require('fs')
    const curriculum = require('./promiseExercise1')
    describe('c0d3 lessons', () => {
    it('fs.writeFile should only run once', async () => {
    fetch.mockClear()
    fs.writeFile = jest.fn()
    fetch.mockReturnValue(
    Promise.resolve({
    json: () => {
    return [{ title: 'testing1' }, { title: 'testing2' }]
    }
    })
    )
    await curriculum.getLessons()
    expect(fetch.mock.calls.length).toEqual(1)
    expect(fs.writeFile.mock.calls[0][1]).toEqual(
    '<h1>testing1</h1><h1>testing2</h1>'
    )
    })
    })
  2. Shape

    module.exports = {
    getLessons: () => {
    // your code here...
    }
    }
  3. Explanation

    • Require fetch and fs.
    • Send a fetch request to get the lesson titles.
    • Attach h1 tags around lesson titles.
    • Run the fs.writeFile function.
  4. Code

    const fetch = require('node-fetch')
    const fs = require('fs')
    fetch('https://c0d3.com/api/lessons')
    .then(res => {
    return res.json()
    })
    .then(data => {
    const titles = data.reduce((acc, f) => {
    return `${acc}<h1>${f.title}</h1>`
    }, '')
    fs.writeFile('lessons.html', titles, () => {})
    })
  1. Modify your solution to exercise 3 from the previous section to use axios or node-fetch: Send a request to https://pokeapi.co/api/v2/pokemon/ and write all the names into a file called names.html with the following content:

    <h1>name1</h1><h1>name2</h1>...<h1>nameX</h1>
Answer
  1. Test

    jest.mock('node-fetch')
    jest.mock('fs')
    const fetch = require('node-fetch')
    const fs = require('fs')
    const pokemonNames = require('./promiseExercise2')
    describe('Pokemons', () => {
    it('should print only two pokemon names', async () => {
    fetch.mockClear()
    fs.writeFile = jest.fn()
    fetch.mockReturnValue(
    Promise.resolve({
    json: () => {
    return {
    results: [{ name: 'testing1' }, { name: 'testing2' }]
    }
    }
    })
    )
    await pokemonNames.getPokemons()
    expect(fetch.mock.calls.length).toEqual(1)
    expect(fs.writeFile.mock.calls[0][1]).toEqual(
    '<h1>testing1</h1><h1>testing2</h1>'
    )
    })
    })
  2. Shape

    module.exports = {
    getPokemons: () => {
    // your code here...
    }
    }
  3. Explanation

    • Require fetch and fs.
    • Send fetch request to get list of pokemons.
    • Attach h1 tags to the pokemon names.
    • Run the fs.writeFile function.
  4. Code

    const fetch = require('node-fetch')
    const fs = require('fs')
    fetch('https://pokeapi.co/api/v2/pokemon/')
    .then(res => {
    return res.json()
    })
    .then(data => {
    const names = data.results.reduce((acc, f) => {
    return `${acc}<h1>${f.name}</h1>`
    }, '')
    fs.writeFile('names.html', names, () => {})
    })
  1. Modify your solution to exercise 4 from the previous section to use axios or node-fetch: Send a request to https://api.openaq.org/v1/countries to get all countries and console.log the country with the largest number of cities.
Answer
  1. Test

    jest.mock('node-fetch')
    const fetch = require('node-fetch')
    const data = require('./promiseExercise3')
    describe('Countries', () => {
    it('should print the country with most cities', async () => {
    fetch.mockClear()
    console.log = jest.fn()
    fetch.mockReturnValue(
    Promise.resolve({
    json: () => {
    return {
    results: [
    {
    name: 'Narnia',
    cities: 100
    },
    {
    name: 'SpaceJam',
    cities: 48
    },
    {
    name: 'Pluto',
    cities: 250
    },
    {
    name: 'Galaxy',
    cities: 20
    }
    ]
    }
    }
    })
    )
    await data.getCountries()
    expect(fetch.mock.calls.length).toEqual(1)
    expect(console.log.mock.calls[0][0]).toEqual('Pluto')
    })
    })
  2. Shape

    module.exports. = {
    getCountries: () =>{
    // your code here..
    }
    }
  3. Explanation

    • Require fetch.
    • Send fetch request to get all the countries.
    • Console.log the country with most cities.
  4. Code

    const fetch = require('node-fetch')
    const apiUrl = 'https://api.openaq.org/v1/countries'
    fetch(apiUrl)
    .then(res => {
    return res.json()
    })
    .then(data => {
    const max = data.results.reduce((acc, f) => {
    if (f.cities > acc.cities) {
    return f
    }
    return acc
    }, data.results[0])
    console.log(max.name)
    })
  1. Modify your solution to exercise 5 from the previous section to use axios or node-fetch: Send a request to https://pokeapi.co/api/v2/pokemon/ and the URLs of each of the first 20 Pokemon, and console.log the Pokemon that weighs the most.
Answer
  1. Test

    jest.mock('node-fetch')
    const fetch = require('node-fetch')
    const data = require('./promiseExercise4')
    describe('pokemons', () => {
    it('should console.log the heaviest pokemon', async () => {
    fetch.mockClear()
    const results = [
    {
    results: [
    {
    name: 'testing1',
    url: 'url1'
    },
    {
    name: 'testing2',
    url: 'url2'
    },
    {
    name: 'testing3',
    url: 'url3'
    }
    ]
    },
    [{ weight: '250' }, { weight: '120' }, { weight: '360' }]
    ]
    let resultsIndex = 0
    console.log = jest.fn()
    fetch.mockReturnValue(
    Promise.resolve({
    json: () => {
    const result = results[resultsIndex]
    resultsIndex + 1
    return result
    }
    })
    )
    await data.getPokemons()
    console.log(
    'number of times fetch gets called:',
    fetch.mock.calls.length
    )
    expect(fetch.mock.calls.length).toEqual(4)
    })
    })
  2. Shape

    module.exports = {
    getPokemons: () => {
    // your code here...
    }
    }
  3. Explanation

    • Require fetch.
    • Send fetch request to get all the pokemons.
    • Find and console.log the heaviest pokemon.
  4. Code

    const fetch = require('node-fetch')
    fetch('https://pokeapi.co/api/v2/pokemon')
    .then(response => {
    return response.json()
    })
    .then(data => {
    const fetchPromises = data.results.map(pokemon => {
    return fetch(pokemon.url).then(pokeRes => {
    return pokeRes.json()
    })
    })
    return Promise.all(fetchPromises)
    })
    .then(dataList => {
    return dataList.reduce((acc, pokemon) => {
    if (acc.weight > pokemon.weight) {
    return acc
    }
    return pokemon
    }, dataList[0])
    })
    .then(pokemon => {
    console.log(pokemon.name)
    })
  1. Create an HTML file that sends a request to [https://www.c0d3.com/api/lessons](https://www.c0d3.com/api/lessons) and displays each lesson title inside an h1 tag.
Hint

Click the link at the beginning of the problem, and view the page source if you get stuck.

You'll want to accomplish this in steps.

  1. Send a request to get all the lessons. Make sure you can console.log the array.
  2. Create a string and set a div's innerHTML to the string.
  3. Add an onclick event to each element containing the title.
  1. Create an HTML page of all the countries in the world: https://country.register.gov.uk/records.json?page-size=500. Each country's official-name should be inside h1 tags. When you click on a name, alert the citizen-names property of that country.
Hint

Pay close attention to how this API returns its data—the format is a little more complex than the previous APIs we saw.

Hacking and Debugging

Almost everything that happens in your browser involves sending requests. To look at all the requests that a website sends out, you can open your console and look at the Network tab. Click on each request to find out more.

Sometimes, developers do not build secure websites. We have prepared a gaming website that does not have proper security.

Exercise: Open up the Network tab in your developer console.

Play this game and win it. After you input your name, observe what request your browser sends to save your score. Change the URL and add your username with a score of 1s to the leaderboard.

Answer

Go to Console and add a fetch request with the correct request query parameters.

Master your skill by solving challenges

Complete the last two JS3 challenges

Edit this page on Github