One place for hosting & domains

      Understanding

      Understanding Hoisting in JavaScript


      Introduction

      In this tutorial, we’ll investigate how the famed hoisting mechanism occurs in JavaScript. Before we dive in, let’s get to grips with what hoisting is.

      Hoisting is a JavaScript mechanism where variables and function declarations are moved to the top of their scope before code execution.

      Inevitably, this means that no matter where functions and variables are declared, they are moved to the top of their scope regardless of whether their scope is global or local.

      Of note however, is the fact that the hoisting mechanism only moves the declaration. The assignments are left in place.

      If you’ve ever wondered why you were able to call functions before you wrote them in your code, then read on!

      undefined vs ReferenceError

      Before we begin in earnest, let’s deliberate on a few things.

      console.log(typeof variable); // Output: undefined
      

      This brings us to our first point of note:

      In JavaScript, an undeclared variable is assigned the value undefined at execution and is also of type undefined.

      Our second point is:

      console.log(variable); // Output: ReferenceError: variable is not defined
      

      In JavaScript, a ReferenceError is thrown when trying to access a previously undeclared variable.

      The behaviour of JavaScript when handling variables becomes nuanced because of hoisting. We’ll look at this in depth in subsequent sections.

      Hoisting variables

      The following is the JavaScript lifecycle and indicative of the sequence in which variable declaration and initialisation occurs.

      variable-hoisting

      However, since JavaScript allows us to both declare and initialise our variables simultaneously, this is the most used pattern:

      var a = 100;
      

      It is however important to remember that in the background, JavaScript is religiously declaring then initialising our variables.

      As we mentioned before, all variable and function declarations are hoisted to the top of their scope. I should also add that variable declarations are processed before any code is executed.

      However, in contrast, undeclared variables do not exist until code assigning them is executed.
      Therefore, assigning a value to an undeclared variable implicitly creates it as a global variable when the assignment is executed. This means that, all undeclared variables are global variables.

      To demonstrate this behaviour, have a look at the following:

      function hoist() {
        a = 20;
        var b = 100;
      }
      
      hoist();
      
      console.log(a); 
      /* 
      Accessible as a global variable outside hoist() function
      Output: 20
      */
      
      console.log(b); 
      /*
      Since it was declared, it is confined to the hoist() function scope.
      We can't print it out outside the confines of the hoist() function.
      Output: ReferenceError: b is not defined
      */
      

      Since this is one of the eccentricities of how JavaScript handles variables, it is recommended to always declare variables regardless of whether they are in a function or global scope. This clearly delineates how the interpreter should handle them at run time.

      ES5

      var

      The scope of a variable declared with the keyword var is its current execution context. This is either the enclosing function or for variables declared outside any function, global.
      Let’s look at a few examples to identify what this means:

      global variables

      console.log(hoist); // Output: undefined
      
      var hoist="The variable has been hoisted.";
      
      

      We expected the result of the log to be: ReferenceError: hoist is not defined, but instead, its output is undefined.

      Why has this happened?

      This discovery brings us closer to wrangling our prey.

      JavaScript has hoisted the variable declaration. This is what the code above looks like to the interpreter:

      var hoist;
      
      console.log(hoist); // Output: undefined
      hoist="The variable has been hoisted.";
      

      Because of this, we can use variables before we declare them. However, we have to be careful because the hoisted variable is initialised with a value of undefined.
      The best option would be to declare and initialise our variable before use.

      Function scoped variables

      As we’ve seen above, variables within a global scope are hoisted to the top of the scope. Next, let’s look at how function scoped variables are hoisted.

      function hoist() {
        console.log(message);
        var message="Hoisting is all the rage!"
      }
      
      hoist();
      

      Take an educated guess as to what our output might be.

      If you guessed, undefined you’re right. If you didn’t, worry not, we’ll soon get to the bottom of this.

      This is how the interpreter views the above code:

      function hoist() {
        var message;
        console.log(message);
        message="Hoisting is all the rage!"
      }
      
      hoist(); // Ouput: undefined
      

      The variable declaration, var message whose scope is the function hoist(), is hoisted to the top of the function.

      To avoid this pitfall, we would make sure to declare and initialise the variable before we use it:

      function hoist() {
        var message="Hoisting is all the rage!"
        return (message);
      }
      
      hoist(); // Ouput: Hoisting is all the rage!
      

      Strict Mode

      Thanks to a utility of the es5 version of JavaScript known as strict-mode, we can be more careful about how we declare our variables.
      By enabling strict mode, we opt into a restricted variant of JavaScript that will not tolerate the usage of variables before they are declared.

      Running our code in strict mode:

      1. Eliminates some silent JavaScript errors by changing them to explicit throw errors which will be spit out by the interpreter.
      2. Fixes mistakes that make it difficult for JavaScript engines to perform optimisations.
      3. Prohibits some syntax likely to be defined in future versions of JavaScript.

      We enable strict mode by prefacing our file or function with

      'use strict';
      
      // OR
      "use strict";
      

      Let’s test it out.

      'use strict';
      
      console.log(hoist); // Output: ReferenceError: hoist is not defined
      hoist="Hoisted"; 
      

      We can see that instead of assuming that we missed out on declaring our variable, use strict has stopped us in our tracks by explicitly throwing a Reference error. Try it out without use strict and see what happens.

      Strict mode behaves differently in different browsers however, so it’s advisable to perform feature testing thoroughly before relying on it in production.

      ES6

      ECMAScript 6, ECMAScript 2015 also known as ES6 is the latest version of the ECMAScript standard, as the writing of this article, Jan 2017 and introduces a few changes to es5.

      Of interest to us is how changes in the standard affect the declaration and initialisation of JavaScript variables.

      let

      Before we start, to be noted is the fact that variables declared with the keyword let are block scoped and not function scoped. That’s significant, but it shouldn’t trouble us here. Briefly, however, it just means that the variable’s scope is bound to the block in which it is declared and not the function in which it is declared.

      Let’s start by looking at the let keyword’s behaviour.

      console.log(hoist); // Output: ReferenceError: hoist is not defined ...
      let hoist="The variable has been hoisted.";
      

      Like before, for the var keyword, we expect the output of the log to be undefined. However, since the es6 let doesn’t take kindly on us using undeclared variables, the interpreter explicitly spits out a Reference error.

      This ensures that we always declare our variables first.

      However, we still have to be careful here. An implementation like the following will result in an ouput of undefined instead of a Reference error.

      let hoist;
      
      console.log(hoist); // Output: undefined
      hoist="Hoisted"
      

      Hence, to err on the side of caution, we should declare then assign our variables to a value before using them.

      const

      The const keyword was introduced in es6 to allow immutable variables. That is, variables whose value cannot be modified once assigned.

      With const, just as with let, the variable is hoisted to the top of the block.

      Let’s see what happens if we try to reassign the value attached to a const variable.

      const PI = 3.142;
      
      PI = 22/7; // Let's reassign the value of PI
      
      console.log(PI); // Output: TypeError: Assignment to constant variable.
      

      How does const alter variable declaration? Let’s take a look.

      console.log(hoist); // Output: ReferenceError: hoist is not defined
      const hoist="The variable has been hoisted.";
      

      Much like the let keyword, instead of silently exiting with an undefined, the interpreter saves us by explicitly throwing a Reference error.

      The same occurs when using const within functions.

      function getCircumference(radius) {
        console.log(circumference)
        circumference = PI*radius*2;
        const PI = 22/7;
      }
      
      getCircumference(2) // ReferenceError: circumference is not defined
      

      With const , es6 goes further. The interpreter throws an error if we use a constant before declaring and initialising it.

      Our linter is also quick to inform us of this felony:

      PI was used before it was declared, which is illegal for const variables.
      

      Globally,

      
      const PI;
      console.log(PI); // Ouput: SyntaxError: Missing initializer in const declaration
      PI=3.142;
      

      Therefore, a constant variable must be both declared and initialised before use.


      As a prologue to this section, it’s important to note that indeed, JavaScript hoists variables declared with es6 let and const. The difference in this case is how it initialises them.
      Variables declared with let and const remain uninitialised at the beginning of execution whilst variables declared with var are initialised with a value of undefined.

      Hoisting functions

      JavaScript functions can be loosely classified as the following:

      1. Function declarations
      2. Function expressions

      We’ll investigate how hoisting is affected by both function types.

      Function declarations

      These are of the following form and are hoisted completely to the top. Now, we can understand why JavaScript enable us to invoke a function seemingly before declaring it.

      hoisted(); // Output: "This function has been hoisted."
      
      function hoisted() {
        console.log('This function has been hoisted.');
      };
      

      Function expressions

      Function expressions, however are not hoisted.

      expression(); //Output: "TypeError: expression is not a function
      
      var expression = function() {
        console.log('Will this work?');
      };
      

      Let’s try the combination of a function declaration and expression.

      expression(); // Ouput: TypeError: expression is not a function
      
      var expression = function hoisting() {
        console.log('Will this work?');
      };
      

      As we can see above, the variable declaration var expression is hoisted but it’s assignment to a function is not. Therefore, the intepreter throws a TypeError since it sees expression as a variable and not a function.

      Order of precedence

      It’s important to keep a few things in mind when declaring JavaScript functions and variables.

      1. Variable assignment takes precedence over function declaration
      2. Function declarations take precedence over variable declarations

      Function declarations are hoisted over variable declarations but not over variable assignments.

      Let’s take a look at what implications this behaviour has.

      Variable assignment over function declaration

      var double = 22;
      
      function double(num) {
        return (num*2);
      }
      
      console.log(typeof double); // Output: number
      

      Function declarations over variable declarations

      var double;
      
      function double(num) {
        return (num*2);
      }
      
      console.log(typeof double); // Output: function
      

      Even if we reversed the position of the declarations, the JavaScript interpreter would still consider double a function.

      Hoisting classes

      JavaScript classes too can be loosely classified either as:

      1. Class declarations
      2. Class expressions

      Class declarations

      Much like their function counterparts, JavaScript class declarations are hoisted. However, they remain uninitialised until evaluation.
      This effectively means that you have to declare a class before you can use it.

      
      var Frodo = new Hobbit();
      Frodo.height = 100;
      Frodo.weight = 300;
      console.log(Frodo); // Output: ReferenceError: Hobbit is not defined
      
      class Hobbit {
        constructor(height, weight) {
          this.height = height;
          this.weight = weight;
        }
      }
      

      I’m sure you’ve noticed that instead of getting an undefined we get a Reference error. That evidence lends claim to our position that class declarations are hoisted.

      If you’re paying attention to your linter, it supplies us with a handy tip.

      Hobbit was used before it is declared, which is illegal for class variables
      

      So, as far as class declarations go, to access the class declaration, you have to declare first.

      class Hobbit {
        constructor(height, weight) {
          this.height = height;
          this.weight = weight;
        }
      }
      
      var Frodo = new Hobbit();
      Frodo.height = 100;
      Frodo.weight = 300;
      console.log(Frodo); // Output: { height: 100, weight: 300 }
      

      Class expressions

      Much like their function counterparts, class expressions are not hoisted.

      Here’s an example with the un-named or anonymous variant of the class expression.

      var Square = new Polygon();
      Square.height = 10;
      Square.width = 10;
      console.log(Square); // Output: TypeError: Polygon is not a constructor
      
      var Polygon = class {
        constructor(height, width) {
          this.height = height;
          this.width = width;
        }
      };
      

      Here’s an example with a named class expression.

      var Square = new Polygon();
      Square.height = 10;
      Square.width = 10;
      console.log(Square); // Output: TypeError: Polygon is not a constructor
      
      
      var Polygon = class Polygon {
        constructor(height, width) {
          this.height = height;
          this.width = width;
        }
      };
      
      

      The correct way to do it is like this:

      var Polygon = class Polygon {
        constructor(height, width) {
          this.height = height;
          this.width = width;
        }
      };
      
      var Square = new Polygon();
      Square.height = 10;
      Square.width = 10;
      console.log(Square);
      

      Caveat

      There’s a bit of an argument to be made as to whether Javascript es6 let, const variables and classes are actually hoisted, roughly hoisted or not hoisted. Some argue that they are actually hoisted but uninitialised whilst some argue that they are not hoisted at all.

      Conclusion

      Let’s summarise what we’ve learned so far:

      1. While using es5 var, trying to use undeclared variables will lead to the variable being assigned a value of undefined upon hoisting.
      2. While using es6 let and const, using undeclared variables will lead to a Reference Error because the variable remains uninitialised at execution.

      Therefore,

      1. We should make it a habit to declare and initialise JavaScript variables before use.
      2. Using strict mode in JavaScript es5 can help expose undeclared variables.

      I hope this article will serve as a good introduction to the concept of hoisting in JavaScript and spur your interest regarding the subtleties of the JavaScript language.



      Source link

      Understanding SQL Constraints


      Introduction

      When designing a database, there may be times when you want to put limits on what data is allowed in certain columns. For example, if you’re creating a table that will hold information on skyscrapers, you may want the column holding each building’s height to prohibit negative values.

      Relational database management systems (RDBMSs) allow you to control what data gets added to a table with constraints. A constraint is a special rule that applies to one or more columns — or to an entire table — that restricts what changes can be made to a table’s data, whether through an INSERT, UPDATE, or DELETE statement.

      This article will review in detail what constraints are and how they’re used in RDBMSs. It will also walk through each of the five constraints defined in the SQL standard and explain their respective functions.

      What Are Constraints?

      In SQL, a constraint is any rule applied to a column or table that limits what data can be entered into it. Any time you attempt to perform an operation that changes that data held in a table — such as an INSERT, UPDATE, or DELETE statement — the RDBMS will test whether that data violates any existing constraints and, if so, return an error.

      Database administrators often rely on constraints to ensure that a database follows a set of defined business rules. In the context of a database, a business rule is any policy or procedure that a business or other organization follows and that its data must adhere to as well. For instance, say you’re building a database that will catalog a client’s store inventory. If the client specifies that each product record should have a unique identification number, you could create a column with a UNIQUE constraint that will ensure no two entries in that column are the same.

      Constraints are also helpful with maintaining data integrity. Data integrity is a broad term that’s often used to describe the overall accuracy, consistency, and rationality of data held in a database, based on its particular use case. Tables in a database are often closely related, with columns in one table being dependent on the values in another. Because data entry is often prone to human error constraints are useful in cases like this, as they can help ensure that no incorrectly entered data could impact such relationships and thus harm the database’s data integrity.

      Imagine you’re designing a database with two tables: one for listing current students at a school and another for listing members of that school’s basketball team. You could apply a FOREIGN KEY constraint to a column in the basketball team table which refers to a column in the school table. This will establish a relationship between the two tables by requiring any entry to the team table to refer to an existing entry in the students table.

      Users define constraints when they first create a table, or they can add them later on with an ALTER TABLE statement as long as it doesn’t conflict with any data already in the table. When you create a constraint, the database system will generate a name for it automatically, but in most SQL implementations you can add a custom name for any constraint. These names are used to refer to constraints in ALTER TABLE statements when changing or removing them.

      The SQL standard formally defines just five constraints:

      • PRIMARY KEY
      • FOREIGN KEY
      • UNIQUE
      • CHECK
      • NOT NULL

      Note: Many RDBMSs include the DEFAULT keyword, which is used to define a default value for a column other than NULL if no value is specified when inserting a row. The documentation of some of these database management systems refer to DEFAULT as a constraint, as their implementations of SQL use a DEFAULT syntax similar to that of constraints like UNIQUE or CHECK. However, DEFAULT technically is not a constraint since it doesn’t restrict what data can be entered into a column.

      Now that you have a general understanding of how constraints are used, let’s take a closer look at each of these five constraints.

      PRIMARY KEY

      The PRIMARY KEY constraint requires every entry in the given column to be both unique and not NULL, and allows you to use that column to identify each individual row in the table

      In the relational model, a key is a column or set of columns in a table in which every value is guaranteed to be unique and to not contain any NULL values. A primary key is a special key whose values are used to identify individual rows in a table, and the column or columns that comprise the primary key can be used to identify the table throughout the rest of the database.

      This is an important aspect of relational databases: with a primary key, users don’t need to know where their data is physically stored on a machine and their DBMS can keep track of each record and return them on an ad hoc basis. In turn, this means that records have no defined logical order, and users have the ability to return their data in whatever order or through whatever filters they wish.

      You can create a primary key in SQL with the PRIMARY KEY constraint, which is essentially a combination of the UNIQUE and NOT NULL constraints. After defining a primary key, the DBMS will automatically create an index associated with it. An index is a database structure that helps to retrieve data from a table more quickly. Similar to an index in a textbook, queries only have to review entries from the indexed column to find the associated values. This is what allows the primary key to act as an identifier for each row in the table.

      A table can only have one primary key but, like regular keys, a primary key can comprise more than one column. With that said, a defining feature of primary keys is that they use only the minimal set of attributes needed to uniquely identify each row in a table. To illustrate this idea, imagine a table that stores information about students at a school using the following three columns:

      • studentID: used to hold each student’s unique identification number
      • firstName: used to hold each student’s first name
      • lastName: used to hold each student’s last name

      It’s possible that some students at the school could share a first name, making the firstName column a poor choice of a primary key. The same is true for the lastName column. A primary key consisting of both the firstName and lastName columns could work, but there’s still a possibility that two students could share a first and last name.

      A primary key consisting of the studentID and either the firstName or lastName columns could work, but since each student’s identification number is already known to be unique, including either of the name columns in the primary key would be superfluous. So in this case the minimal set of attributes that can identify each row, and would thus be a good choice for the table’s primary key, is just the studentID column on its own.

      If a key is made up of observable application data (that is, data that represents real world entities, events, or attributes) it’s referred to as a natural key. If the key is generated internally and doesn’t represent anything outside the database, it’s known as a surrogate or synthetic key. Some database systems recommend against using natural keys, as even seemingly constant data points can change in unpredictable ways.

      FOREIGN KEY

      The FOREIGN KEY constraint requires that every entry in the given column must already exist in a specific column from another table.

      If you have two tables that you’d like to associate with one another, one way you can do so is by defining a foreign key with the FOREIGN KEY constraint. A foreign key is a column in one table (the “child” table) whose values come from a key in another table (the “parent”). This is a way to express a relationship between two tables: the FOREIGN KEY constraint requires that values in the column on which it applies must already exist in the column that it references.

      The following diagram highlights such a relationship between two tables: one used to record information about employees at a company and another used to track the company’s sales. In this example, the primary key of the EMPLOYEES table is referenced by the foreign key of the SALES table:

      Diagram example of how the EMPLOYEE table's primary key acts as the SALES table's foreign key

      If you try to add a record to the child table and the value entered into the foreign key column doesn’t exist in the parent table’s primary key, the insertion statement will be invalid. This helps to maintain relationship-level integrity, as the rows in both tables will always be related correctly.

      Oftentimes, a table’s foreign key is the parent table’s primary key, but this isn’t always the case. In most RDBMSs, any column in the parent table that has a UNIQUE or PRIMARY KEY constraint applied to it can be referenced by the child table’s foreign key.

      UNIQUE

      The UNIQUE constraint prohibits any duplicate values from being added to the given column.

      As its name implies, a UNIQUE constraint requires every entry in the given column to be a unique value. Any attempt to add a value that already appears in the column will result in an error.

      UNIQUE constraints are useful for enforcing one-to-one relationships between tables. As mentioned previously, you can establish a relationship between two tables with a foreign key, but there are multiple kinds of relationships that can exist between tables:

      • one-to-one: Two tables are said to have a one-to-one relationship if rows in the parent table are related to one and only one row in the child table
      • one-to-many: In a many-to-any relationship, a row in the parent table can relate to multiple rows in the child table, but each row in the child table can only relate to one row in the parent
      • many-to-many: If rows in the parent table can relate to multiple rows in the child table, and vice versa, the two are said to have a many-to-many relationship

      By adding a UNIQUE constraint to a column on which a FOREIGN KEY constraint has been applied, you can ensure that each entry in the parent table appears only once in the child, thereby establishing a one-to-one relationship between the two tables.

      Note that you can define UNIQUE constraints at the table level as well as the column level. When defined at the table level, a UNIQUE constraint can apply to more than one column. In cases like this, each column included in the constraint can have duplicate values but every row must have a unique combination of values in the constrained columns.

      CHECK

      A CHECK constraint defines a requirement for a column, known as a predicate, that every value entered into it must meet.

      CHECK constraint predicates are written in the form of an expression that can evaluate to either TRUE, FALSE, or potentially UNKNOWN. If you attempt to enter a value into a column with a CHECK constraint and the value causes the predicate to evaluate to TRUE or UNKNOWN (which happens for NULL values), the operation will succeed. However, if the expression resolves to FALSE, it will fail.

      CHECK predicates often rely on a mathematical comparison operator (like <, >, <=, OR >=) to limit the range of data allowed into the given column. For instance, one common use for CHECK constraints is to prevent certain columns holding negative values in cases where a negative value wouldn’t make sense, as in the following example.

      This CREATE TABLE statement creates a table named productInfo with columns for each product’s name, identification number, and price. Because it wouldn’t make sense for a product to have a negative price, this statement imposes a CHECK constraint on the price column to ensure that it only contains positive values:

      • CREATE TABLE productInfo (
      • productID int,
      • name varchar(30),
      • price decimal(4,2)
      • CHECK (price > 0)
      • );

      Not every CHECK predicate must use a mathematical comparison operator. Typically, you can use any SQL operator that can evaluate to TRUE, FALSE, or UNKNOWN in a CHECK predicate, including LIKE, BETWEEN, IS NOT NULL, and others. Some SQL implementations, but not all, even allow you to include a subquery in a CHECK predicate. Be aware, though, that most implementations do not allow you to reference another table in a predicate.

      NOT NULL

      The NOT NULL constraint prohibits any NULL values from being added to the given column.

      In most implementations of SQL, if you add a row of data but don’t specify a value for a certain column, the database system will by default represent the missing data as NULL. In SQL, NULL is a special keyword used to represent an unknown, missing, or otherwise unspecified value. However, NULL is not a value itself but instead the state of an unknown value.

      To illustrate this difference, imagine a table used to track clients at a talent agency that has columns for each client’s first and last names. If a client goes by a mononym — like “Cher”, “Usher”, or “Beyoncé” — the database administrator might only enter the mononym in the first name column, causing the DBMS to enter NULL in the last name column. The database doesn’t consider the client’s last name to literally be “Null.” It just means that the value for that row’s last name column is unknown or the field doesn’t apply for that particular record.

      As its name implies, the NOT NULL constraint prevents any values in the given column from being NULL. This means that for any column with a NOT NULL constraint, you must specify a value for it when inserting a new row. Otherwise, the INSERT operation will fail.

      Conclusion

      Constraints are essential tools for anyone looking to design a database with a high level of data integrity and security. By limiting what data gets entered into a column, you can ensure that relationships between tables will be maintained correctly and that the database adheres to the business rules that define its purpose.

      For more detailed information on how to create and manage SQL constraints, you can review our guide on How To Use Constraints in SQL. If you’d like to learn more about SQL in general, we encourage you to check out our series on How To Use SQL.



      Source link

      Understanding the Event Loop, Callbacks, Promises, and Async/Await in JavaScript


      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      In the early days of the internet, websites often consisted of static data in an HTML page. But now that web applications have become more interactive and dynamic, it has become increasingly necessary to do intensive operations like make external network requests to retrieve API data. To handle these operations in JavaScript, a developer must use asynchronous programming techniques.

      Since JavaScript is a single-threaded programming language with a synchronous execution model that processes one operation after another, it can only process one statement at a time. However, an action like requesting data from an API can take an indeterminate amount of time, depending on the size of data being requested, the speed of the network connection, and other factors. If API calls were performed in a synchronous manner, the browser would not be able to handle any user input, like scrolling or clicking a button, until that operation completes. This is known as blocking.

      In order to prevent blocking behavior, the browser environment has many Web APIs that JavaScript can access that are asynchronous, meaning they can run in parallel with other operations instead of sequentially. This is useful because it allows the user to continue using the browser normally while the asynchronous operations are being processed.

      As a JavaScript developer, you need to know how to work with asynchronous Web APIs and handle the response or error of those operations. In this article, you will learn about the event loop, the original way of dealing with asynchronous behavior through callbacks, the updated ECMAScript 2015 addition of promises, and the modern practice of using async/await.

      Note: This article is focused on client-side JavaScript in the browser environment. The same concepts are generally true in the Node.js environment, however Node.js uses its own C++ APIs as opposed to the browser’s Web APIs. For more information on asynchronous programming in Node.js, check out How To Write Asynchronous Code in Node.js.

      The Event Loop

      This section will explain how JavaScript handles asynchronous code with the event loop. It will first run through a demonstration of the event loop at work, and will then explain the two elements of the event loop: the stack and the queue.

      JavaScript code that does not use any asynchronous Web APIs will execute in a synchronous manner—one at a time, sequentially. This is demonstrated by this example code that calls three functions that each print a number to the console:

      // Define three example functions
      function first() {
        console.log(1)
      }
      
      function second() {
        console.log(2)
      }
      
      function third() {
        console.log(3)
      }
      

      In this code, you define three functions that print numbers with console.log().

      Next, write calls to the functions:

      // Execute the functions
      first()
      second()
      third()
      

      The output will be based on the order the functions were called—first(), second(), then third():

      Output

      1 2 3

      When an asynchronous Web API is used, the rules become more complicated. A built-in API that you can test this with is setTimeout, which sets a timer and performs an action after a specified amount of time. setTimeout needs to be asynchronous, otherwise the entire browser would remain frozen during the waiting, which would result in a poor user experience.

      Add setTimeout to the second function to simulate an asynchronous request:

      // Define three example functions, but one of them contains asynchronous code
      function first() {
        console.log(1)
      }
      
      function second() {
        setTimeout(() => {
          console.log(2)
        }, 0)
      }
      
      function third() {
        console.log(3)
      }
      

      setTimeout takes two arguments: the function it will run asynchronously, and the amount of time it will wait before calling that function. In this code you wrapped console.log in an anonymous function and passed it to setTimeout, then set the function to run after 0 milliseconds.

      Now call the functions, as you did before:

      // Execute the functions
      first()
      second()
      third()
      

      You might expect with a setTimeout set to 0 that running these three functions would still result in the numbers being printed in sequential order. But because it is asynchronous, the function with the timeout will be printed last:

      Output

      1 3 2

      Whether you set the timeout to zero seconds or five minutes will make no difference—the console.log called by asynchronous code will execute after the synchronous top-level functions. This happens because the JavaScript host environment, in this case the browser, uses a concept called the event loop to handle concurrency, or parallel events. Since JavaScript can only execute one statement at a time, it needs the event loop to be informed of when to execute which specific statement. The event loop handles this with the concepts of a stack and a queue.

      Stack

      The stack, or call stack, holds the state of what function is currently running. If you’re unfamiliar with the concept of a stack, you can imagine it as an array with “Last in, first out” (LIFO) properties, meaning you can only add or remove items from the end of the stack. JavaScript will run the current frame (or function call in a specific environment) in the stack, then remove it and move on to the next one.

      For the example only containing synchronous code, the browser handles the execution in the following order:

      • Add first() to the stack, run first() which logs 1 to the console, remove first() from the stack.
      • Add second() to the stack, run second() which logs 2 to the console, remove second() from the stack.
      • Add third() to the stack, run third() which logs 3 to the console, remove third() from the stack.

      The second example with setTimout looks like this:

      • Add first() to the stack, run first() which logs 1 to the console, remove first() from the stack.
      • Add second() to the stack, run second().
        • Add setTimeout() to the stack, run the setTimeout() Web API which starts a timer and adds the anonymous function to the queue, remove setTimeout() from the stack.
      • Remove second() from the stack.
      • Add third() to the stack, run third() which logs 3 to the console, remove third() from the stack.
      • The event loop checks the queue for any pending messages and finds the anonymous function from setTimeout(), adds the function to the stack which logs 2 to the console, then removes it from the stack.

      Using setTimeout, an asynchronous Web API, introduces the concept of the queue, which this tutorial will cover next.

      Queue

      The queue, also referred to as message queue or task queue, is a waiting area for functions. Whenever the call stack is empty, the event loop will check the queue for any waiting messages, starting from the oldest message. Once it finds one, it will add it to the stack, which will execute the function in the message.

      In the setTimeout example, the anonymous function runs immediately after the rest of the top-level execution, since the timer was set to 0 seconds. It’s important to remember that the timer does not mean that the code will execute in exactly 0 seconds or whatever the specified time is, but that it will add the anonymous function to the queue in that amount of time. This queue system exists because if the timer were to add the anonymous function directly to the stack when the timer finishes, it would interrupt whatever function is currently running, which could have unintended and unpredictable effects.

      Note: There is also another queue called the job queue or microtask queue that handles promises. Microtasks like promises are handled at a higher priority than macrotasks like setTimeout.

      Now you know how the event loop uses the stack and queue to handle the execution order of code. The next task is to figure out how to control the order of execution in your code. To do this, you will first learn about the original way to ensure asynchronous code is handled correctly by the event loop: callback functions.

      Callback Functions

      In the setTimeout example, the function with the timeout ran after everything in the main top-level execution context. But if you wanted to ensure one of the functions, like the third function, ran after the timeout, then you would have to use asynchronous coding methods. The timeout here can represent an asynchronous API call that contains data. You want to work with the data from the API call, but you have to make sure the data is returned first.

      The original solution to dealing with this problem is using callback functions. Callback functions do not have special syntax; they are just a function that has been passed as an argument to another function. The function that takes another function as an argument is called a higher-order function. According to this definition, any function can become a callback function if it is passed as an argument. Callbacks are not asynchronous by nature, but can be used for asynchronous purposes.

      Here is a syntactic code example of a higher-order function and a callback:

      // A function
      function fn() {
        console.log('Just a function')
      }
      
      // A function that takes another function as an argument
      function higherOrderFunction(callback) {
        // When you call a function that is passed as an argument, it is referred to as a callback
        callback()
      }
      
      // Passing a function
      higherOrderFunction(fn)
      

      In this code, you define a function fn, define a function higherOrderFunction that takes a function callback as an argument, and pass fn as a callback to higherOrderFunction.

      Running this code will give the following:

      Output

      Just a function

      Let’s go back to the first, second, and third functions with setTimeout. This is what you have so far:

      function first() {
        console.log(1)
      }
      
      function second() {
        setTimeout(() => {
          console.log(2)
        }, 0)
      }
      
      function third() {
        console.log(3)
      }
      

      The task is to get the third function to always delay execution until after the asynchronous action in the second function has completed. This is where callbacks come in. Instead of executing first, second, and third at the top-level of execution, you will pass the third function as an argument to second. The second function will execute the callback after the asynchronous action has completed.

      Here are the three functions with a callback applied:

      // Define three functions
      function first() {
        console.log(1)
      }
      
      function second(callback) {
        setTimeout(() => {
          console.log(2)
      
          // Execute the callback function
          callback()
        }, 0)
      }
      
      function third() {
        console.log(3)
      }
      

      Now, execute first and second, then pass third as an argument to second:

      first()
      second(third)
      

      After running this code block, you will receive the following output:

      Output

      1 2 3

      First 1 will print, and after the timer completes (in this case, zero seconds, but you can change it to any amount) it will print 2 then 3. By passing a function as a callback, you’ve successfully delayed execution of the function until the asynchronous Web API (setTimeout) completes.

      The key takeaway here is that callback functions are not asynchronous—setTimeout is the asynchronous Web API responsible for handling asynchronous tasks. The callback just allows you to be informed of when an asynchronous task has completed and handles the success or failure of the task.

      Now that you have learned how to use callbacks to handle asynchronous tasks, the next section explains the problems of nesting too many callbacks and creating a “pyramid of doom.”

      Nested Callbacks and the Pyramid of Doom

      Callback functions are an effective way to ensure delayed execution of a function until another one completes and returns with data. However, due to the nested nature of callbacks, code can end up getting messy if you have a lot of consecutive asynchronous requests that rely on each other. This was a big frustration for JavaScript developers early on, and as a result code containing nested callbacks is often called the “pyramid of doom” or “callback hell.”

      Here is a demonstration of nested callbacks:

      function pyramidOfDoom() {
        setTimeout(() => {
          console.log(1)
          setTimeout(() => {
            console.log(2)
            setTimeout(() => {
              console.log(3)
            }, 500)
          }, 2000)
        }, 1000)
      }
      

      In this code, each new setTimeout is nested inside a higher order function, creating a pyramid shape of deeper and deeper callbacks. Running this code would give the following:

      Output

      1 2 3

      In practice, with real world asynchronous code, this can get much more complicated. You will most likely need to do error handling in asynchronous code, and then pass some data from each response onto the next request. Doing this with callbacks will make your code difficult to read and maintain.

      Here is a runnable example of a more realistic “pyramid of doom” that you can play around with:

      // Example asynchronous function
      function asynchronousRequest(args, callback) {
        // Throw an error if no arguments are passed
        if (!args) {
          return callback(new Error('Whoa! Something went wrong.'))
        } else {
          return setTimeout(
            // Just adding in a random number so it seems like the contrived asynchronous function
            // returned different data
            () => callback(null, {body: args + ' ' + Math.floor(Math.random() * 10)}),
            500,
          )
        }
      }
      
      // Nested asynchronous requests
      function callbackHell() {
        asynchronousRequest('First', function first(error, response) {
          if (error) {
            console.log(error)
            return
          }
          console.log(response.body)
          asynchronousRequest('Second', function second(error, response) {
            if (error) {
              console.log(error)
              return
            }
            console.log(response.body)
            asynchronousRequest(null, function third(error, response) {
              if (error) {
                console.log(error)
                return
              }
              console.log(response.body)
            })
          })
        })
      }
      
      // Execute 
      callbackHell()
      

      In this code, you must make every function account for a possible response and a possible error, making the function callbackHell visually confusing.

      Running this code will give you the following:

      Output

      First 9 Second 3 Error: Whoa! Something went wrong. at asynchronousRequest (<anonymous>:4:21) at second (<anonymous>:29:7) at <anonymous>:9:13

      This way of handling asynchronous code is difficult to follow. As a result, the concept of promises was introduced in ES6. This is the focus of the next section.

      Promises

      A promise represents the completion of an asynchronous function. It is an object that might return a value in the future. It accomplishes the same basic goal as a callback function, but with many additional features and a more readable syntax. As a JavaScript developer, you will likely spend more time consuming promises than creating them, as it is usually asynchronous Web APIs that return a promise for the developer to consume. This tutorial will show you how to do both.

      Creating a Promise

      You can initialize a promise with the new Promise syntax, and you must initialize it with a function. The function that gets passed to a promise has resolve and reject parameters. The resolve and reject functions handle the success and failure of an operation, respectively.

      Write the following line to declare a promise:

      // Initialize a promise
      const promise = new Promise((resolve, reject) => {})
      

      If you inspect the initialized promise in this state with your web browser’s console, you will find it has a pending status and undefined value:

      Output

      __proto__: Promise [[PromiseStatus]]: "pending" [[PromiseValue]]: undefined

      So far, nothing has been set up for the promise, so it’s going to sit there in a pending state forever. The first thing you can do to test out a promise is fulfill the promise by resolving it with a value:

      const promise = new Promise((resolve, reject) => {
        resolve('We did it!')
      })
      

      Now, upon inspecting the promise, you’ll find that it has a status of fulfilled, and a value set to the value you passed to resolve:

      Output

      __proto__: Promise [[PromiseStatus]]: "fulfilled" [[PromiseValue]]: "We did it!"

      As stated in the beginning of this section, a promise is an object that may return a value. After being successfully fulfilled, the value goes from undefined to being populated with data.

      A promise can have three possible states: pending, fulfilled, and rejected.

      • Pending – Initial state before being resolved or rejected
      • Fulfilled – Successful operation, promise has resolved
      • Rejected – Failed operation, promise has rejected

      After being fulfilled or rejected, a promise is settled.

      Now that you have an idea of how promises are created, let’s look at how a developer may consume these promises.

      Consuming a Promise

      The promise in the last section has fulfilled with a value, but you also want to be able to access the value. Promises have a method called then that will run after a promise reaches resolve in the code. then will return the promise’s value as a parameter.

      This is how you would return and log the value of the example promise:

      promise.then((response) => {
        console.log(response)
      })
      

      The promise you created had a [[PromiseValue]] of We did it!. This value is what will be passed into the anonymous function as response:

      Output

      We did it!

      So far, the example you created did not involve an asynchronous Web API—it only explained how to create, resolve, and consume a native JavaScript promise. Using setTimeout, you can test out an asynchronous request.

      The following code simulates data returned from an asynchronous request as a promise:

      const promise = new Promise((resolve, reject) => {
        setTimeout(() => resolve('Resolving an asynchronous request!'), 2000)
      })
      
      // Log the result
      promise.then((response) => {
        console.log(response)
      })
      

      Using the then syntax ensures that the response will be logged only when the setTimeout operation is completed after 2000 milliseconds. All this is done without nesting callbacks.

      Now after two seconds, it will resolve the promise value and it will get logged in then:

      Output

      Resolving an asynchronous request!

      Promises can also be chained to pass along data to more than one asynchronous operation. If a value is returned in then, another then can be added that will fulfill with the return value of the previous then:

      // Chain a promise
      promise
        .then((firstResponse) => {
          // Return a new value for the next then
          return firstResponse + ' And chaining!'
        })
        .then((secondResponse) => {
          console.log(secondResponse)
        })
      

      The fulfilled response in the second then will log the return value:

      Output

      Resolving an asynchronous request! And chaining!

      Since then can be chained, it allows the consumption of promises to appear more synchronous than callbacks, as they do not need to be nested. This will allow for more readable code that can be maintained and verified easier.

      Error Handling

      So far, you have only handled a promise with a successful resolve, which puts the promise in a fulfilled state. But frequently with an asynchronous request you also have to handle an error—if the API is down, or a malformed or unauthorized request is sent. A promise should be able to handle both cases. In this section, you will create a function to test out both the success and error case of creating and consuming a promise.

      This getUsers function will pass a flag to a promise, and return the promise:

      function getUsers(onSuccess) {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            // Handle resolve and reject in the asynchronous API
          }, 1000)
        })
      }
      

      Set up the code so that if onSuccess is true, the timeout will fulfill with some data. If false, the function will reject with an error:

      function getUsers(onSuccess) {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            // Handle resolve and reject in the asynchronous API
            if (onSuccess) {
              resolve([
                {id: 1, name: 'Jerry'},
                {id: 2, name: 'Elaine'},
                {id: 3, name: 'George'},
              ])
            } else {
              reject('Failed to fetch data!')
            }
          }, 1000)
        })
      }
      

      For the successful result, you return JavaScript objects that represent sample user data.

      In order to handle the error, you will use the catch instance method. This will give you a failure callback with the error as a parameter.

      Run the getUser command with onSuccess set to false, using the then method for the success case and the catch method for the error:

      // Run the getUsers function with the false flag to trigger an error
      getUsers(false)
        .then((response) => {
          console.log(response)
        })
        .catch((error) => {
          console.error(error)
        })
      

      Since the error was triggered, the then will be skipped and the catch will handle the error:

      Output

      Failed to fetch data!

      If you switch the flag and resolve instead, the catch will be ignored, and the data will return instead:

      // Run the getUsers function with the true flag to resolve successfully
      getUsers(true)
        .then((response) => {
          console.log(response)
        })
        .catch((error) => {
          console.error(error)
        })
      

      This will yield the user data:

      Output

      (3) [{…}, {…}, {…}] 0: {id: 1, name: "Jerry"} 1: {id: 2, name: "Elaine"} 3: {id: 3, name: "George"}

      For reference, here is a table with the handler methods on Promise objects:

      Method Description
      then() Handles a resolve. Returns a promise, and calls onFulfilled function asynchronously
      catch() Handles a reject. Returns a promise, and calls onRejected function asynchronously
      finally() Called when a promise is settled. Returns a promise, and calls onFinally function asynchronously

      Promises can be confusing, both for new developers and experienced programmers that have never worked in an asynchronous environment before. However as mentioned, it is much more common to consume promises than create them. Usually, a browser’s Web API or third party library will be providing the promise, and you only need to consume it.

      In the final promise section, this tutorial will cite a common use case of a Web API that returns promises: the Fetch API.

      Using the Fetch API with Promises

      One of the most useful and frequently used Web APIs that returns a promise is the Fetch API, which allows you to make an asynchronous resource request over a network. fetch is a two-part process, and therefore requires chaining then. This example demonstrates hitting the GitHub API to fetch a user’s data, while also handling any potential error:

      // Fetch a user from the GitHub API
      fetch('https://api.github.com/users/octocat')
        .then((response) => {
          return response.json()
        })
        .then((data) => {
          console.log(data)
        })
        .catch((error) => {
          console.error(error)
        })
      

      The fetch request is sent to the https://api.github.com/users/octocat URL, which asynchronously waits for a response. The first then passes the response to an anonymous function that formats the response as JSON data, then passes the JSON to a second then that logs the data to the console. The catch statement logs any error to the console.

      Running this code will yield the following:

      Output

      login: "octocat", id: 583231, avatar_url: "https://avatars3.githubusercontent.com/u/583231?v=4" blog: "https://github.blog" company: "@github" followers: 3203 ...

      This is the data requested from https://api.github.com/users/octocat, rendered in JSON format.

      This section of the tutorial showed that promises incorporate a lot of improvements for dealing with asynchronous code. But, while using then to handle asynchronous actions is easier to follow than the pyramid of callbacks, some developers still prefer a synchronous format of writing asynchronous code. To address this need, ECMAScript 2016 (ES7) introduced async functions and the await keyword to make working with promises easier.

      Async Functions with async/await

      An async function allows you to handle asynchronous code in a manner that appears synchronous. async functions still use promises under the hood, but have a more traditional JavaScript syntax. In this section, you will try out examples of this syntax.

      You can create an async function by adding the async keyword before a function:

      // Create an async function
      async function getUser() {
        return {}
      }
      

      Although this function is not handling anything asynchronous yet, it behaves differently than a traditional function. If you execute the function, you’ll find that it returns a promise with a [[PromiseStatus]] and [[PromiseValue]] instead of a return value.

      Try this out by logging a call to the getUser function:

      console.log(getUser())
      

      This will give the following:

      Output

      __proto__: Promise [[PromiseStatus]]: "fulfilled" [[PromiseValue]]: Object

      This means you can handle an async function with then in the same way you could handle a promise. Try this out with the following code:

      getUser().then((response) => console.log(response))
      

      This call to getUser passes the return value to an anonymous function that logs the value to the console.

      You will receive the following when you run this program:

      Output

      {}

      An async function can handle a promise called within it using the await operator. await can be used within an async function and will wait until a promise settles before executing the designated code.

      With this knowledge, you can rewrite the Fetch request from the last section using async/await as follows:

      // Handle fetch with async/await
      async function getUser() {
        const response = await fetch('https://api.github.com/users/octocat')
        const data = await response.json()
      
        console.log(data)
      }
      
      // Execute async function
      getUser()
      

      The await operators here ensure that the data is not logged before the request has populated it with data.

      Now the final data can be handled inside the getUser function, without any need for using then. This is the output of logging data:

      Output

      login: "octocat", id: 583231, avatar_url: "https://avatars3.githubusercontent.com/u/583231?v=4" blog: "https://github.blog" company: "@github" followers: 3203 ...

      Note: In many environments, async is necessary to use await—however, some new versions of browsers and Node allow using top-level await, which allows you to bypass creating an async function to wrap the await in.

      Finally, since you are handling the fulfilled promise within the asynchronous function, you can also handle the error within the function. Instead of using the catch method with then, you will use the try/catch pattern to handle the exception.

      Add the following highlighted code:

      // Handling success and errors with async/await
      async function getUser() {
        try {
          // Handle success in try
          const response = await fetch('https://api.github.com/users/octocat')
          const data = await response.json()
      
          console.log(data)
        } catch (error) {
          // Handle error in catch
          console.error(error)
        }
      }
      

      The program will now skip to the catch block if it receives an error and log that error to the console.

      Modern asynchronous JavaScript code is most often handled with async/await syntax, but it is important to have a working knowledge of how promises work, especially as promises are capable of additional features that cannot be handled with async/await, like combining promises with Promise.all().

      Note: async/await can be reproduced by using generators combined with promises to add more flexibility to your code. To learn more, check out our Understanding Generators in JavaScript tutorial.

      Conclusion

      Because Web APIs often provide data asynchronously, learning how to handle the result of asynchronous actions is an essential part of being a JavaScript developer. In this article, you learned how the host environment uses the event loop to handle the order of execution of code with the stack and queue. You also tried out examples of three ways to handle the success or failure of an asynchronous event, with callbacks, promises, and async/await syntax. Finally, you used the Fetch Web API to handle asynchronous actions.

      For more information about how the browser handles parallel events, read Concurrency model and the event loop on the Mozilla Developer Network. If you’d like to learn more about JavaScript, return to our How To Code in JavaScript series.



      Source link