Skip to main content

Lesson 6 - JavaScript 3: Scope and Asynchronous JS

Functions‚Äč

Two kinds of functions:

  • Regular function
  • Arrow function

Regular Function‚Äč

Here's a regular JS function defined via function keyword:

function adder(x, y) {
return x + y;
}
  • Regular function has this keyword and also arguments binding.
    • We will be covering this and arguments binding later.

Arrow Function‚Äč

  • Similar to the lambda function in python, arrow function is used to make code simpler and more elegant.
  • Arrow function does not have its own this, argument, new keyword.
  • Do not use arrow function for objects.
    • Use them for callbacks!
const add = (x, y) => {
return x + y;
};

// ...or
const add = (x, y) => x + y;

Scope‚Äč

Scope is the accessibility of variables, functions, and objects in some particular part of your code during runtime.

Four types of scope in JS:

  • Global Scope
  • Local Scope (function)
  • Block Scope (curly bracket: if)
  • Lexical Scope (nested function)

Global Scope‚Äč

Any variables / functions defined outside of a function becomes GLOBAL scope.

// Global scope
var carName = 'Tesla';

function myFunction() {
// local scope
var myName = 'Alex';
}

Local Scope‚Äč

Any variables / functions defined inside of a function becomes LOCAL scope. You cannot access a local scope variable within global scope. However, you can access a global scope variable through local scope!

var a = 20;

function change() {
a = 25;
}

console.log(a); // 25?

Block Scope‚Äč

Anything that defined inside a curly bracket (if, else, ...) is in BLOCK scope.

Devops Image

var vs. let vs. const‚Äč

Before we talk about lexical scope, let's look at the differences between var vs. let vs. const.

Scope of var is either global or local. When you define a var in the local scope, you cannot access it in global scope. However, when you define a var in the global scope, you can access it through local scope and update it if you want.

var tester = 'hey hi';

function newFunction() {
var hello = 'hello';
}

console.log(hello); // error: hello is not defined

var can also be redeclared and updated and won't get an error.

var greeter = 'hey hi';
var greeter = 'say Hello instead';

However, this creates a problem! If you have unknowingly declare a variable that shares the same name with another previous variable, you would change the value of that previous variable unintentionally! The compiler would also not catch that error.

  • For instance, in the example below, someone has created a temporary username variable inside the if statement, that username variable overwrite the previous username variable.
var username = 'Alex';
var shouldFetch = true;

if (shouldFetch) {
var username = 'Bob';
fetch(username);
}

console.log(username); // "Bob"

As a result, we have let and const.

let and const cannot be redeclared, which means that we would catch those errors right at the compiling stage.

  • let cannot be redeclared in a function / block scope

Also, they are block scoped, meaning that things defined in a if statement would not influence things outside the if statement.

Devops Image

Lexical Scope‚Äč

A lexical scope in JavaScript means that a variable defined outside a function can be accessible inside another function defined after the variable declaration. But the opposite is not true; the variables defined inside a function will not be accessible outside that function.

Devops Image

  • the relationship between global & local scope can also be considered as a kind of lexical scope.

this‚Äč

This keyword has multiple different meanings in different context in javascript. Essentially, it is a keyword that refers to the object it belongs to.

this in a method‚Äč

When you use this in a method of an object, it refers to the owner of the method, in our case, it is the obj.

However, when using this in a method, it should only be used in a regular function rather than an arrow function, since arrow function does not have a this binding.

var obj = {
i: 10,
b: () => console.log(this.i, this),
c: function () {
console.log(this.i, this);
},
};

obj.b(); // prints undefined, Window {...} (or the global object)
obj.c(); // prints 10, Object {...}

this alone‚Äč

When used alone, the owner is the Global object, so this refers to the Global object.

In a browser window the Global object is [object Window]:

this in arrow function‚Äč

Though arrow function doesn't have a this binding, it doesn't mean that we cannot use this in an arrow function.

In this example, we use an arrow function inside the setTimeout function, and it correctly produce the age in Person as 42. The reason behind this is that since arrow function does not have a this binding, it searches the value of "this" upwards from its parent scope. As a result, this.age = 42 is found and used.

window.age = 10;

function Person() {
this.age = 42;
setTimeout(() => {
console.log('this.age', this.age); // will yield 42.
}, 1000);
}

If we use a normal function here, we would have the result 10.

Asynchronous‚Äč

Introduction‚Äč

In a synchronous programming model, things happen one at a time. When you call a function that performs a long-running action, it returns only when the action has finished and it can return the result. This stops your program for the time the action takes.

An asynchronous model allows multiple things to happen at the same time. When you start an action, your program continues to run. When the action finishes, the program is informed and gets access to the result (for example, the data read from a server).

Devops Image

This is especially important for javascript, since we will be doing asynchronous action (fetching data) a lot! Javascript also relies on asynchronous rather than thread model.

Callbacks‚Äč

One approach to asynchronous programming is to make functions that perform a slow action take an extra argument, a callback function. The action is started, and when it finishes, the callback function is called with the result.

As an example, the setTimeout function waits a given number of milliseconds (a second is a thousand milliseconds) and then calls a function.

setTimeout(() => console.log('Tick'), 500);

We can nest multiple callbacks with one another, to handle multiple asynchronous data. For instance, if we want to fetch a data from a server, and then process the data and load it to the user:

// fetchData(username, callback)
// processData(data, callback)
// loadData(data, callback)

fetchData(username, callback) {
result = fetch(username)
callback(result)
}

fetchData(username, (response) => {
processData(response, (processedData) => {
loadData(processedData);
});
});

Here, the function fetchData takes two arguments: the username and a callback function. This callback function will be called once the action of fetchData is finished.

The function processData, likewise, also takes two arguments: the response from fetchData and a callback function.

As you can see, we can chain multiple callbacks together to form a large callback chain to process asynchronous data.

Promises‚Äč

Callbacks are great. However, when the callback chains get too long, it will be very hard to maintain. Promise is another way to write asynchronous code without using callbacks.

A promise is an object that may produce a single value some time in the future: either a resolved value, or a reason that it’s not resolved.

A promise may be in one of 3 possible states: fulfilled, rejected, or pending. Promise users can attach callbacks to handle the fulfilled value or the reason for rejection.

Devops Image

To handle a promise, you can use .then method of a promise to pass in two functions, one for the promise that resolves and one for the promise that rejects.

Example‚Äč

Our wait(3000) call will wait 3000ms (3 seconds), and then log 'Hello!'. All spec-compatible promises define a .then() method which you use to pass handlers which can take the resolved or rejected value.

The ES6 promise constructor takes a function. That function takes two parameters, resolve(), and reject(). In the example above, we’re only using resolve(), so I left reject() off the parameter list. Then we call setTimeout() to create the delay, and call resolve() when it’s finished.

You can optionally resolve() or reject() with values, which will be passed to the callback functions attached with .then().

const wait = (time) => new Promise((resolve) => setTimeout(resolve, time));

const responsePromise = wait(3000); // this is a promise returned by the wait function
responsePromise.then(
() => console.log('Hello!'),
() => console.log('error')
); // 'Hello!'

Fetch‚Äč

Fetch API is a way for us to fetch data from another API. This gives us a great chance to practice our newly learned asynchronous coding skill!

Fetch takes a request url and will send a HTTP request to that particular url, fetching all the related data from that API.

It will return a promise, which then we can resolve it by using .then method.

fetch(some_random_url)
.then((response) => response.json())
.then((data) => console.log(data));

Let's try fetch something from the NASA API!

NASA Open APIs

fetch('https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY')
.then((response) => response.json())
.then((data) => console.log(data));

Resources‚Äč

Asynchronous Programming :: Eloquent JavaScript

Master the JavaScript Interview: What is a Promise?


Contributors