JS Interview Help (part 3): Apply/Call/Bind, Async JS, Callback, Promises, Async & Await

Please note — this is a BRIEF explanation, meant for 2–3 minute interview answers. Please use the resources after the article for a deeper understanding on each topic.

Picture of Ernie and Lucy, the wiener dog
Picture of Ernie and Lucy, the wiener dog

For this lesson we’re going to use Lucy, my childhood wiener dog, in each example! Lucy was a wild dog at first who calmed down with age. She would constantly hunt down birds in our backyard, kill garden snakes, and bark at any dog that passed by! While she may have been ferocious, she was also incredibly gentle and loved us.

Apply/Call/Bind

Bind

const module = {
lucysDeadSnakes: 2,
getLucysDeadSnakes: function() {
return this.lucysDeadSnakes + ' dead snakes!';
}
};
const unboundGetLucysDeadSnakes = module.getLucysDeadSnakes;
console.log(unboundGetLucysDeadSnakes());
// undefined dead snakes!
const boundGetLucysDeadSnakes = unboundGetLucysDeadSnakes.bind(module);
console.log(boundGetLucysDeadSnakes());
//2 dead snakes!

The JS engine creates a new boundGetLucysDeadSnakes instance and binding module as its this variable. It copies the getLucysDeadSnakes function. After creating a copy of this function, it’s able to call boundGetLucysDeadSnakes although it wasn’t on the module object at first. It now recognizes lucysDeadSnakes as its properties and its methods.

Call/Apply

const lucy = {
first: 'Lucy',
last: 'Bade',
twoSnakes: function() {
let name = this.first + ' ' + this.last;
return name;
}
};
let killedAnimals = function(animal, number) {
console.log(this.twoSnakes() + ' killed ' + number + ' ' + animal + 's today!');
};
killedAnimals.call(lucy, 'snake', 2);
//Lucy Bade killed 2 snakes today!
killedAnimals.apply(lucy, ['snake', 2]);
//Lucy Bade killed 2 snakes today!

Async JS

const lucysTasks = ['hunt', 'nap', 'dinner', 'bath'];

Lucy could be done synchronously, in this exact order. This means she must execute each task in their exact order, and she can’t move on to another task until each task is done. This method brings some questions:

  • What if hunting takes longer than expected, and while it’s 9 pm she’s only hunted 1 snake? If Lucy were thinking in a synchronous way, she would have to keep hunting until she finds the 2nd snake. It may take an hour, or a whole week, and Lucy would be malnourished, sleepy-deprived, and worst of all, incredibly smelly.
  • What if one task takes a long time to prepare (like filling a bath tub with warm water), while shorter tasks (like eating dinner) can be done while the tub is filling up?

This is where asynchronous work comes into play. Rather than executing Lucy’s tasks in order, she can hunt for a little bit, nap for 30 minutes, eat dinner, hunt some more, then take a bath. Rather than focusing on one task at a time, Lucy is focusing on ALL of her tasks, and making sure they are finished by the end of the day.

The JavaScript engine loves asynchronous work. JavaScript looks at multiple lines of code, and asks the question, “How can I do ALL of this work in the most efficient, least-time-consuming, way possible?” By working asynchronously, JavaScript can fetch some data from an external device/API, and continue executing other functions while the JavaScript engine waits on that data.

So, while Lucy’s bath water is filling up, she can happily enjoy her dinner!

Callback

To explain callbacks, we’re going to reference Lucy’s ability to destroy all kinds of toys, and count the number of toys she’s destroyed this past week. To set up this example, we have an array that contains the number of toys Lucy destroyed last week:

let lucysDestruction = [1, 2, 4, 7, 3, 5, 6];

Now, our goal is to reduce this array into one number, which is the total number of toys Lucy has destroyed. So, our function should look something like:

lucysDestruction = [1 + 2, 4, 7, 3, 5, 6];
lucysDestruction = [3 + 4, 7, 3, 5, 6];
lucysDestruction = [7 + 7, 3, 5, 6];
lucysDestruction = [14 + 3, 5, 6];
lucysDestruction = [17 + 5, 6];
lucysDestruction = [22 + 6];
lucysDestruction = 28;

So, we can utilize the reduce method, which requires a callback to reduce our array into a single value! First off, we create a callback method which reduce will call on:

function isOddNumber(lucysDestruction, currentNumberOfToys) {
return lucysDestruction + currentNumberOfToys;
}

Then, we pass this function as a callback to the reduce method:

const oddNumbers = lucysDestruction.reduce(isOddNumber);

If we console.log oddNumbers, our result will be…

console.log(oddNumbers);
// 28

All of this together looks like:

let lucysDestruction = [1, 2, 4, 7, 3, 5, 6];function isOddNumber(lucysDestruction, currentNumberOfToys) {
return lucysDestruction + currentNumberOfToys;
}
const oddNumbers = lucysDestruction.reduce(isOddNumber);console.log(oddNumbers);
// 28

Let’s break this down a little bit. In the first line, we declare the lucysDestruction variable, which points to an array with 7 elements, containing the number of toys she destroyed on each day. Then, we declare a function, isOddNumber, which returns lucysDestruction and an accumulator, which keeps an active tally of the currentNumberOfToys we’ve counted. Then, we declare an oddNumbers variable which declares lucysDestruction with a reducer, which utilizes the isOddNumber callback.

When we run console.log(oddNumbers), we’re asking the JavaScript to reduce lucysDestruction. In order to do this, the reduce method needs a callback (which we declared as isOddNumber). The reduce method is basically saying, “Hey, isOddNumber, can you do this for me and I’ll return the final value?” isOddNumber helps out and the reduce method works because of its callback.

Callbacks are everywhere in JavaScript. Some of the most popular methods that use callbacks are map, reduce, sort, and filter. Callbacks really shine in asynchronous functions, where one function has to wait for another function, like waiting for a file to load.

Promises

A promise can be returned synchronously from an asynchronous function and it’s always in one of the three above states (pending, fulfilled, or rejected). There are some rules that promises follow:

  • A promise or “thenable” is an object that supplies a standard-compliant .then() method.
  • A pending promise may transition into a fulfilled or rejected state.
  • A fulfilled or rejected promise is settled, and must not transition into any other state.
  • Once a promise is settled, it must have a value (which may be undefined). That value must not change.

The most frequent example (in my short, limited experience) of working with promises comes from working with fetch requests. Let’s say we’re trying to find a random friend for Lucy to hang out with. We start with a basic fetch request:

let fetchedDogs = fetch('https://dog.ceo/api/breeds/image/random');fetchedDogs;
// Promise {<fulfilled>}

Now, we can chain the .then() method. For our intents and purposes, we’ll see what is actually retrieved through this method:

fetchedDogs
.then(resp => console.log(resp));
// Response { type: "basic", url: "...", redirected: false, status: 200, ... }
// Promise {<fulfilled>}

If we convert the above object into JSON format using the .json() method, we can add another .then statement to get Lucy’s random dog friends:

fetchedDogs
.then(resp => resp.json())
.then(dogs => console.log(dogs));
// {message: 'image-of-dog.jpg', status: 'success'}

The Promise in each response returns a success status, meaning that it was able to send to our console. If the URL was incorrect, server shut down, or a variety of other issues happened, the returned Promise would have returned “rejected,” and Lucy’s friend would not be found. However, right now, if Lucy got a “rejected” promise, she wouldn’t know why! To help resolve this, we add a catch to the end of the .then chained methods. For example, let’s add an incorrect URL:

fetch('https://dog.ceo/api/breeds/image/bones')
.then(resp => resp.json())
.then(dogs => console.log(dogs))
.catch(error => console.log(error));
// {status: 'error', message: 'No route found for "GET /api/breeds/image/bones....," code: 404}

Now, if Lucy gets side-tracked and adds ‘bones’ to the end of her URL fetch request, she can see where her error is!

Async & Await

const request = () => 
promisedJSON()
.then(data => {
console.log(data)
return 'done'
})
request()

The above code can be translated to async/await as:

const request = async () => {
console.log(await promisedJSON())
return 'done'
}
request()

There are some differences here, beyond the number of lines of code:

  • Our request function has the async keyword before it, and await can only be used in functions with async. await promisedJSON() implies that the console.log call will wait until promisedJSON() promise resolves and prints its value.

The code is cleaner, handles errors better, handles conditionals more easily, handles intermediate values, makes debugging easier, and you can await anything.

Sorry, this example didn’t include an example with Lucy… I guess Lucy will have to (a)wait for her turn.

Resources:

Async JS

Callback

Promises

Async & Await

Thanks for reading! To view Michael’s portfolio, click here. Michael is a recent Flatiron School graduate, open for work, and always happy to talk code. Let’s connect on LinkedIn! Questions or comments are always welcome!

For part 1 of this series on JavaScript interview help, click here.
For part 2 of this series on JavaScript interview help,
click here.

Michael Bade is a Full Stack Web Developer, with a passion for making abstract ideas come to life! Find me on LinkedIn to connect and talk code!