DEV Community

Michael Otu
Michael Otu

Posted on

JavaScript Essentials: Part 5

Previously in JavaScript Essentials: Part 4, We discussed if and else statements, for and while loops. In this part, we will look at:

  • Functions
  • Callbacks, promises, async & await
  • Next big thing

Comments

Comments are great and we are now going to talk about it. It is so late that you should know what a comment is. Anyway, a comment in our program is not executed. A comment is meant to document our code. There are three ways to add comments in Javascript. We have the inline, multiline and JsDoc.

In-line

// this is a number
const numberOfBirds = 3;

// the above comment is useless since the initial value assigned to the variable
// is physically a number and the variable name also has a number in it
// so use comments wisely by using proper naming
Enter fullscreen mode Exit fullscreen mode

Multiline

/* 
Everything in here is or will be ignored.

Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.


*/

const emptyString = "";
Enter fullscreen mode Exit fullscreen mode

JsDoc

/**
 * This is a multiline comment
 *
 * But used for documentation
 */
Enter fullscreen mode Exit fullscreen mode

Comments can be placed anywhere; however, be careful when placing them after (or at the end) a line of code or below or above it.

Semi-colon

In javascript, semi-colon, ;, is not required however, it helps sometimes. There are tools that help you with it. A semi-colon indicates the end of a statement. Good.

Indentation

Indentations are used to arrange code for clarity and ease of reading. The tab key (on the keyboard) is used in indenting. Indentations are sometimes "tabs" or "spaces". The space is usually 2 or 4. If you are using vscode, you don't have to worry.

Examples

There were some exercises from JavaScript Essentials: Part 4 which included but were not limited to "fizzbuzz", password and email validation, etc. If you were to have followed my pseudocode, you'd run into some issues. I will provide a snippet that considers the order.

fizzbuzz for a single number

const givenNumber = 3;

if (givenNumber % 3 === 0 && givenNumber % 5 === 0) {
  console.log("fizzbuzz");
} else if (givenNumber % 3 === 0) {
  console.log("fizz");
} else if (givenNumber % 5 === 0) {
  console.log("buzz");
}
Enter fullscreen mode Exit fullscreen mode

fizzbuzz for an array

const numbers = [3, 6, 10, 15];

for (const givenNumber of numbers) {
  if (givenNumber % 3 === 0 && givenNumber % 5 === 0) {
    console.log("fizzbuzz");
  } else if (givenNumber % 3 === 0) {
    console.log("fizz");
  } else if (givenNumber % 5 === 0) {
    console.log("buzz");
  }
}
Enter fullscreen mode Exit fullscreen mode

password validation

const veryWeakPassword = "PrQ1V_";
// const veryWeakPassword = "rtfy67Fg";
// const veryWeakPassword = "OlJgRc__1qwPVa";
console.log(`Password validation for "${veryWeakPassword}"`);

// - be six characters
if (veryWeakPassword.length !== 6) {
  console.log(
    `- Password must have 6 characters => "${veryWeakPassword}" has '${veryWeakPassword.length}' characters`
  );
}
// - start with uppercase p, 'P'
else if (!veryWeakPassword.startsWith("P")) {
  console.log(
    `- Password must start with 'P' => it is ${veryWeakPassword.startsWith(
      "P"
    )} that "${veryWeakPassword}" starts with 'P'`
  );
}
// - end with underscore
else if (!veryWeakPassword.endsWith("_")) {
  console.log(
    `- Password must end with '_' => it is ${veryWeakPassword.endsWith(
      "_"
    )} that "${veryWeakPassword}" ends with '_'`
  );
}
// - have uppercase q, 'Q'
else if (!veryWeakPassword.includes("Q")) {
  console.log(
    `- Password must have uppercase q, 'Q' => it is ${veryWeakPassword.includes(
      "Q"
    )} that "${veryWeakPassword}" has 'Q'`
  );
}
// - have lowercase r, 'r'
else if (!veryWeakPassword.includes("r")) {
  console.log(
    `- Password must have lowercase r, 'r' => it is ${veryWeakPassword.includes(
      "r"
    )} that "${veryWeakPassword}" has 'r'`
  );
}
// - have its fifth character as uppercase v, 'V'
// fifth character with have index = fifth position - 1 = 4
// const fifthCharacter = veryWeakPassword[4]
else if (veryWeakPassword.charAt(4) !== "V") {
  console.log(
    `- Password must have its fifth character as uppercase v, 'V' => "${veryWeakPassword}" has its 5th character as '${veryWeakPassword.charAt(
      4
    )}'`
  );
} else {
  console.log(`${veryWeakPassword} is a valid password`);
}
Enter fullscreen mode Exit fullscreen mode

Some other solutions would be using nested if and else.

// password validation
const veryWeakPassword = "PrQ1V_";
// const veryWeakPassword = "rtfy67Fg";
// const veryWeakPassword = "OlJgRc__1qwPVa";
console.log(`Password validation for "${veryWeakPassword}"`);

// - be six characters
if (veryWeakPassword.length === 6) {
  if (veryWeakPassword.startsWith("P")) {
    if (veryWeakPassword.endsWith("_")) {
      if (veryWeakPassword.includes("Q")) {
        if (veryWeakPassword.includes("r")) {
          if (veryWeakPassword.charAt(4) === "V") {
            console.log(`${veryWeakPassword} is a valid password`);
          } else {
            console.log(
              `- Password must have its fifth character as uppercase v, 'V' => "${veryWeakPassword}" has its 5th character as '${veryWeakPassword.charAt(
                4
              )}'`
            );
          }
        } else {
          console.log(
            `- Password must have lowercase r, 'r' => it is ${veryWeakPassword.includes(
              "r"
            )} that "${veryWeakPassword}" has 'r'`
          );
        }
      } else {
        console.log(
          `- Password must have uppercase q, 'Q' => it is ${veryWeakPassword.includes(
            "Q"
          )} that "${veryWeakPassword}" has 'Q'`
        );
      }
    } else {
      console.log(
        `- Password must end with '_' => it is ${veryWeakPassword.endsWith(
          "_"
        )} that "${veryWeakPassword}" ends with '_'`
      );
    }
  } else {
    console.log(
      `- Password must start with 'P' => it is ${veryWeakPassword.startsWith(
        "P"
      )} that "${veryWeakPassword}" starts with 'P'`
    );
  }
} else {
  console.log(
    `- Password must have 6 characters => "${veryWeakPassword}" has '${veryWeakPassword.length}' characters`
  );
}
Enter fullscreen mode Exit fullscreen mode

What do you think about the two snippets? Practically the second snippet, even though it works, is not that great.

Functions

A function is a piece of code that can be reused. Usually, a function does a specific thing. One thing. It can be anything.

Let's look at the general form (structure) of a function in JavaScript.

function functionName(/* parameters */) {
  // do something
}
Enter fullscreen mode Exit fullscreen mode
  • function is a keyword required when creating a function. The for keyword is needed when you want to use a for loop.
  • functionName is supposed to be the name given to the function. The idea of naming a variable applies to a function.
  • /* parameters */ refers to the data you want to pass to the function.
  • // do something is the action or computation we desire to be performed. Functions usually return data after some processing is done. There are times when it doesn't. It just updates some data and is done.
  • { // do something } is the functions body or block

We can have a function that prints "hello world"

// function to print "hello world"
function printHelloWorld() {
  console.log("Hello world");
}
Enter fullscreen mode Exit fullscreen mode

We did ourselves the favour to name our function with a name that describes what the function does.

Now, when we have a function, we have to "call" it for it to be executed. To call a function, you need the function's name followed by ( and ). If the function takes a parameter, you'd pass the argument in the ( and ). In our case, for the "hello world" function, we have to do, printHelloWorld();.

printHelloWorld();

// the output of this function will be on the console/terminal
Enter fullscreen mode Exit fullscreen mode

Let's move in a little direction that will broaden our arsenal and make creating functions fun. Consider this function that adds two numbers and then prints a text telling you what happened.

function add() {
  const x = 3;
  const y = 20;

  const sum = x + y;

  console.log(`${x} + ${y} = ${sum}`);
}

add(); // 3 + 20 = 23
Enter fullscreen mode Exit fullscreen mode

Is this giving you ideas? We can write our "fizzbuzz" and validations using functions. We can be so stingy and delicate that we'd write each validation requirement as a function. It happens. Just don't overdo it.

Now, consider the add function. What if we want to add different numbers, what do we do? We can create another function that. We can also alter the values directly. Right? Yeah. You will be amazed by what we can accomplish with functions.

First of all, if we want to add different numbers we can change the numbers.

function add() {
  const x = 10;
  const y = 2;

  const sum = x + y;

  console.log(`${x} + ${y} = ${sum}`);
}

add(); // 10 + 2 = 12
Enter fullscreen mode Exit fullscreen mode

Okay, let's alter the function to add 6 and 100 rather. Now we have to alter the function. There is a solution to this and it is to introduce parameters (data via variables). Then we'd pass those data as arguments.

Let's analyze our add function. The add function operates on x and y and operands. We can pass different values to x and y by passing x and y as parameters.

function add(x, y) {
  // const x = 3;
  // const y = 20;

  const sum = x + y;

  console.log(`${x} + ${y} = ${sum}`);
}

add(3, 30);
add(10, 2);
add(6, 100);
// 3 + 30 = 33
// 10 + 2 = 12
// 6 + 100 = 106
Enter fullscreen mode Exit fullscreen mode

Instead of having the values of x and y as internal values in add, we pass them. Now the difference between parameters and arguments is that parameters are passed when creating (defining) the function. Arguments are the values passed when calling the function. So in function add(x, y), x and y are parameters (we can say placeholders, representing the data to be passed to the function). In add(3, 30);, 3 and 30 are passed as arguments (the actual values to be processed). Note that the order of the argument and parameters must match else we'd be in serious debt.

Do you think it is enough to take on the big guns? Well, I think you can. You just have to be calm and know what you are doing. I will provide some snippets.

function passwordValidation(password) {
  // write the password validation program here
}

passwordValidation("HillBilly676");
passwordValidation("Nixton009");
passwordValidation("PrQ1V_");
Enter fullscreen mode Exit fullscreen mode

Do the same for the "fizzbuzz". Wrap a function around the snippet. You don't have to comment on the variables used. Look at what data needs to be passed to the function (input).

We can pass as many parameters to a function. However, I'd encourage you to set some limits. There are some professionals who say about three is enough. Some say about fewer than five. You have to be smart about it. For now, let's say that whenever the number of parameters exceeds three, we would use an array or an object. Yeah. We can pass an array or an object as an argument.

// interest = (principal * rate * time) / 100
function calculateInterest(principal, rate, time) {
  const interest = (principal * rate * time) / 100;

  console.log(
    `The interest on \$${principal} for ${time} years at a rate of ${rate}% is \$${interest}`
  );
}

calculateInterest(2000, 0.05, 3);
// The interest on $2000 for 3 years at a rate of 0.05% is $3
calculateInterest(1000, 0.25, 6);
// The interest on $1000 for 6 years at a rate of 0.25% is $15
Enter fullscreen mode Exit fullscreen mode

Write a function that calculates the average of an array of numbers by completing this function.

function printAverage(arrayOfNumbers) {
  // implement your logic here... Your output should match that after the function call.
}

printAverage([1, 2, 3, 4, 5]);
// The average of 1,2,3,4,5 of size 5 is 3
printAverage([9, 8, 0, 6]);
// The average of 9,8,0,6 of size 4 is 5.75
Enter fullscreen mode Exit fullscreen mode

At this point, it should be clear that functions can take arguments. Practically, our functions will return a value or something value after a computation is done. The computed value is returned from the function. A function that returns a value is of the form:

function functionName(/* parameters */) {
  // do something
  // return someValue
}
Enter fullscreen mode Exit fullscreen mode
  • // return someValue is the only new thing here. return is a keyword.
  • someValue is the value returned from the function. And it could be anything to nothing, a void function. Don't sweat it. We will modify some of these functions we have written before so things will be simpler.

Remember the add function? Instead of logging the value inside the function, we will return it and assign that value to a variable then reuse the value later.

function add(x, y) {
  const sum = x + y;

  // usually, we can just do, return x + y and that will also work
  return sum;
}

console.log(`${3} + ${30} = ${add(3, 30)}`);
// 3 + 30 = 33
console.log(`${10} + ${2} = ${add(10, 2)}`);
// 10 + 2 = 12
console.log(`${6} + ${100} = ${add(6, 100)}`);
// 6 + 100 = 106
Enter fullscreen mode Exit fullscreen mode

This is as simple as we can put it. Do the same for the calculateInterest function.

A function can return anything returnable.

Arrow functions

An arrow function is another way to write a function. Usually, I use arrow functions when I have a simple function that does a very minute "thing" or in array or string methods for looping. You can use it in place of the function declarations (named functions). We say, function, to indicate we want to create a function. Arrow functions have the same features as the declarative function.

Arrow functions are called so because of =>, the fat arrow operator. It is of the form, perhaps you've seen before:

const arrowFunctionName = (/* parameter list */) => /* some expression */
Enter fullscreen mode Exit fullscreen mode

or

const arrowFunctionName = (/* parameter list */) => {
  /* some huge logic */
};
Enter fullscreen mode Exit fullscreen mode

Let's rewrite the add function using the arrow function.

const add = (x, y) => x + y;
Enter fullscreen mode Exit fullscreen mode

=>, indicates a return of the value from the x + y express. So the return keyword is used implicitly. Again we can explicitly return a value from the function using the return keyword however, we have to add the functions block.

const add = (x, y) => {
  // usually the body here would be longer
  return x + y;
};
Enter fullscreen mode Exit fullscreen mode

The difference between the two is that in the second, we added a block, { and } and a return keyword that returns a value from the function. Again, you can choose to return a value or not.

Passing functions as arguments

We can pass functions as arguments to other functions. Essentially, we treat functions as values. Let's consider this trivial example.

const callback = (outFunction) => {
  console.log(`${outFunction.name} function was called`);
};

const login = (username, password, logger) => {
  console.log(`localhost://${username}:${password}@weird.com/login`);
  logger(login);
};

const clearAccount = (username, logger) => {
  console.log(`localhost://${username}@weird.com/clear`);
  logger(clearAccount);
};

login("johndoe", "password", callback);
clearAccount("johndoe", callback);
Enter fullscreen mode Exit fullscreen mode

Another place we can do this is with array methods or string methods. Consider this function

const calculateSumOfNumbersInArray = (numericArray) =>
  numericArray.reduce((total, element) => total + element, 0);

const numArray = [1, 2, 3];
const total = calculateSumOfNumbersInArray(numArray);

console.log(`The total of the array, ${numArray} is ${total}`);
// The total of the array, 1,2,3 is 6
Enter fullscreen mode Exit fullscreen mode

We can see that we can pull out the callback function, (total, element) => total + element, 0. In fact, it is the total + element we can replace.

const calculateSumOfNumbersInArray = (numericArray, someFunction) =>
  numericArray.reduce((total, element) => someFunction(total, element), 0);

const add = (x, y) => x + y;

const numArray = [1, 2, 3];
const total = calculateSumOfNumbersInArray(numArray, add);

console.log(`The total of the array, ${numArray} is ${total}`);
// The total of the array, 1,2,3 is 6
Enter fullscreen mode Exit fullscreen mode

You know we pass another function that takes 2 numbers argument and returns a number. We don't even have to create a function.

We have done some maths before but this time we will use functions to abstract the operators.

const add = (x, y) => x + y;

const sub = (x, y) => x - y;

const mul = (x, y) => x * y;

const calculate = (firstOperand, secondOperand, operation) =>
  operation(firstOperand, secondOperand);

console.log(calculate(2, 3, add));
console.log(calculate(2, 3, sub));
console.log(calculate(2, 3, mul));
console.log(calculate(calculate(1, 2, add), calculate(3, 4, sub), mul));
Enter fullscreen mode Exit fullscreen mode

The last parameter is called a default parameter and usually, it is placed as
the last argument. This is something that you have to do if you are going to
use default values. This snippet is not that different from the previous
one except for the introduction of the default parameter which means for the third
argument, we can choose to pass a value for it or not.

const performActionOnArray = (numericArray, someAction, initialValue = 0) => {
  return numericArray.reduce(
    (result, element) => someAction(result, element),
    initialValue
  );
};

const add = (x, y) => x + y;

const numArray = [1, 2, 3];
const total = performActionOnArray(numArray, add);

console.log(`The total of the array, ${numArray} is ${total}`);
// The total of the array, 1,2,3 is 6
Enter fullscreen mode Exit fullscreen mode

In const total = performActionOnArray(numArray, add); we could have passed a function directly

const total = performActionOnArray(
  numArray,
  (value1, value2) => value1 + value2
);
Enter fullscreen mode Exit fullscreen mode

Promise

Before anything, let's define some terms. Promises are important in our niche.

Synchronous operation: These are operations that are executed sequentially, from top to bottom, one after the other. For some operations A1 and A2, A1 has to be completed before A2 will be executed. This way, A2 will not be executed until A1. At a time one operation is executed. This drawback is called blocking.

console.log("Hello there");
const someSyncFunction = () => {
  console.log("I am synchronous");
};
someSyncFunction();
console.log("Bye");
Enter fullscreen mode Exit fullscreen mode

The output for the above is in a linear order as written above.

Hello there
I am synchronous
Bye
Enter fullscreen mode Exit fullscreen mode

In short, the code we have written so far is all executed in a synchronous order and we can tell when one line will be executed.

Asynchronous operations: These are operations that are not executed sequentially. These operations run concurrently. These could be several operations running at the same time, practically, bit by bit. Since the success or execution of one operation is independent of the order and doesn't impede the execution of other lines, we call this behaviour non-blocking. We can not tell when an asynchronous line will be executed.

console.log("Hello there");
setTimeout(() => {
  console.log("I am asynchronous");
}, 2000);
const someSyncFunction = () => {
  console.log("I am synchronous");
};
someSyncFunction();
console.log("Bye");
Enter fullscreen mode Exit fullscreen mode

And this is the output.

Hello there
I am synchronous
Bye
I am asynchronous
Enter fullscreen mode Exit fullscreen mode

Can you identify the async operation based on the output?

It is the setTimeout function. Let's say it runs in the background. It is non-blocking, so the last console.log was executed.

Some Async Operations

  • Network requests (Eg: API calls)
  • Database queries
  • File I/O operations
  • Javascript Web APIs (setTimeout, setInterval, fetch, etc)

A Promise provides a means for managing or handling asynchronous operations. It is a way of knowing the state an async operation is in, when it is executed, and whether it is "fulfilled" or it failed.

Let's create a Promise

A promise has the form:

new Promise((resolve, reject) => {
    // an async operation is executed
    if (/* on completion resolve promise with a value */) {
        resolve(value);
 } else {
        /* on failure reject promise with an error */
        reject(error);
 }
});
Enter fullscreen mode Exit fullscreen mode

new and Promise are keywords. resolve and reject are callback functions. We can replace them with other names. By conversion, we use resolve and reject.

Handle a promise

Promise has then method which provides the resolved value, catch method which provides the rejected error and there is the finally which is a way to clean up after the whole process. finally is optional though. Here is a simple example you can play with.

console.log("Hello there");

const madeAPromise = new Promise((resolve, reject) => {
  // this could have been a database or API call
  const result = true;

  /* on completion resolve promise with a value */
  if (result) {
    resolve("🥦 is good for your health");
  } else {
    /* on failure reject promise with an error */
    reject("Well, 🙄, an error occurred");
  }
});

console.log("After the promise is created");

madeAPromise
  .then((value) => console.log({ value }))
  .catch((error) => console.log({ error }))
  .finally(() =>
    console.log("We have handled the async call, now we can all have pizza")
  );

console.log("Bye");
Enter fullscreen mode Exit fullscreen mode

Look at the output and see how the code is executed. console.log("Bye"); was not the last to be executed. We created our async operation using a promise and handled it using then and catch. If we are thinking of executing these operations in order, then we can or have to put the remaining logic inside the then block.

madeAPromise
  .then((value) => {
    console.log({ value });
    console.log("Bye");
  })
  .catch((error) => console.log({ error }))
  .finally(() =>
    console.log("We have handled the async call, now we can all have pizza")
  );
Enter fullscreen mode Exit fullscreen mode

What happened?

The issue with this approach to handling promises is that we tend to nest or chain this operation, the then block fattens and it is not that friendly. So let's look at async and await.

async and await

In the normal flow of data, we don't want the async operation to run in the background. We want to listen to it and use its result to do something else (as we did in the then and catch).

Let's create an async operation and handle it using async and await.

We know how to create named functions and arrow functions.

function functionName() {}

const functionName = () => {};
Enter fullscreen mode Exit fullscreen mode

To make a function asynchronous, we use the async keyword.

async function functionName() {}

const functionName = async () => {};
Enter fullscreen mode Exit fullscreen mode

Now whatever operation goes into the block of the function and let's say in the async function we want to create, we would have to deal with another async operation, then we can use the await.

async function functionName() {
  const responseFromAsyncOperation = await SomeAsyncOperation(someArguments);
}

const functionName = async () => {
  const responseFromAsyncOperation = await SomeAsyncOperation(someArguments);
};
Enter fullscreen mode Exit fullscreen mode

The await tells javascript to "wait" and receive the resolved or fulfilled value from the promise.

console.log("Hello there");

async function asyncFunction() {
  return new Promise((resolve, reject) => {
    // this could have been a database or API call
    const result = true;

    /* on completion resolve promise with a value */
    if (result) {
      resolve("🥦 is good for your health");
    } else {
      /* on failure reject promise with an error */
      reject("Well, 🙄, an error occurred");
    }
  });
}

console.log("After the promise is created");
const value = await asyncFunction();
console.log({ value });

console.log("Bye");
Enter fullscreen mode Exit fullscreen mode

When we execute the above snippet we get an error similar to, Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension..

We can fix this issue easily. Run the command, npm init -y. Go into the package.json file and add the line, "type": "module". The package.json should look like

{
  "name": "javascript_part_5",
  "version": "1.0.0",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "type": "module"
}
Enter fullscreen mode Exit fullscreen mode

Now, rerun the snippet and you should get an output similar to

Hello there
After the promise is created
{ value: '🥦 is good for your health' }
Bye
Enter fullscreen mode Exit fullscreen mode

Now, let's say we want to handle the promise rejection error, in case there is no, we have to use the try and catch clause around the async call.

console.log("Hello there");

async function asyncFunction() {
  return new Promise((resolve, reject) => {
    // this could have been a database or API call
    const result = true;

    /* on completion resolve promise with a value */
    if (result) {
      resolve("🥦 is good for your health");
    } else {
      /* on failure reject promise with an error */
      reject("Well, 🙄, an error occurred");
    }
  });
}

console.log("After the promise is created");
try {
  const value = await asyncFunction();
  console.log({ value });
} catch (error) {
  console.log({ error });
} finally {
  console.log("We have handled the async call, now we can all have pizza");
}

console.log("Bye");
Enter fullscreen mode Exit fullscreen mode

There won't be any promise rejection because we set, const result = true. Set it to false. And our output should be similar to

Hello there
After the promise is created
{ error: 'Well, 🙄, an error occurred' }
We have handled the async call, now we can all have pizza
Bye
Enter fullscreen mode Exit fullscreen mode

So the purpose of talking about promises and async and await is to let you know that we will be doing that a lot. Refer to the examples of asynchronous operations listed above.

async and await, try and catch and finally are keywords.

Conclusion

At this point where we have discussed functions and promises and how to handle them, I think we are about 50% equipped with the "knowledge" to manipulate data (flow). What is left is to become used to writing javascript code and be a competent javascript programmer. Get your hands dirty with JavaScript. That's the only way you will code a backend API and not feel restrained because you have the idea but don't know what to do.

Next is to write some code and solve some problems then actually start building APIs. That is what we will be doing next, however, I will encourage you to check out the resources below.

Resource

These are materials that will be helpful in understanding Promises, async and await and event loop.

These are some exercises you'd like to try your hands on.

Top comments (1)

Collapse
 
otumianempire profile image
Michael Otu