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.
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
A quick review of “this” before we dive into Apply/Call/Bind: In Object Oriented JS, everything is an object. It’s here where we learn of “this,” which is used inside a function, and always refers to the object that invokes the function where “this” is used. Sometimes, we want to save a “this” reference, which is where Apply/Call/Bind come in!
Bind
The bind()
method creates a new function that, when called, has its this
keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called. Example:
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
The call()
method calls a function with a given this
value and arguments provided individually. (thanks MDN!) SO, we can call any function and specify what this should reference within the calling functino. THe main differences is that the call() method accepts additional parameters as well, executes the function it was called upon right away, and it does not make a copy of the function it is being called on. Call() and apply() are the same. The only difference is that apply() expects an array of all of our parameters, whereas call() expects parameters to be passed in individually. Example:
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
Lucy has an incredibly busy day today. She needs to hunt (and kill…) 2 snakes, she’ll need a nap later, eat dinner, and take a bath. Wow, what a tough life! In Lucy’s mind, she has created a to-do list, and is slowly checking off each one. She’ll start with hunting, then take a nap, eat dinner, and then take a warm, relaxing bath. This may look something like:
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 JavaScript Promise object contains both the producing code and calls to the consuming code. It will produce a single value some time in the future: either a resolved value, or a reason that it’s not resolved. A JavaScript Promise support two properties: state and result. While a Promise object is “pending” (working), the result is undefined. When a Promise object is “fulfilled”, the result is a value. When a Promise object is “rejected”, the result is an error object.
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
The keyword async before a function makes the function return a promise, and the keyword await makes a function wait for a Promise. These are new ways to write asynchronous code, are just syntax sugar built on top of promises, can’t be used with plain callbacks, are non-blocking, and they make asynch code look/behave a little more like synchronous code. Take, for instance, a “normal” request that has a promisedJSON() function which returns a promise that resolves with some JSON:
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:
Apply/Call/Bind:
- https://medium.com/@omergoldberg/javascript-call-apply-and-bind-e5c27301f7bb
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
Async JS
- https://eloquentjavascript.net/11_async.html
- https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Introducing
Callback
Promises
- https://www.w3schools.com/js/js_promise.asp
- https://medium.com/@armando_amador/how-to-make-http-requests-using-fetch-api-and-promises-b0ca7370a444
- https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-promise-27fc71e77261
Async & Await
- https://dev.to/gafi/7-reasons-to-always-use-async-await-over-plain-promises-tutorial-4ej9?ref=hackernoon.com
- https://www.w3schools.com/js/js_async.asp
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.