DEV Community

Cover image for Do you know ES6 - Part 3 - Advanced
Mohamed Khaled Yousef
Mohamed Khaled Yousef

Posted on • Updated on

Do you know ES6 - Part 3 - Advanced

Before you start reading this advanced part, We have discussed some of ES6 features:

Do you know ES6 - Part 1
Do you know ES6 - Part 2

Table Of Contents


IIFE

image

IIFE refers to Immediately Invoked Function Expression. IIFE is a JavaScript function that runs as soon as it is defined. MDN Web Docs

IIFE is differ from the traditional function which we can call it more than once but IIFE isn't. IIFE used only once. So we won't use it again. This means the variables in function can't be accessed so they are immutable.

One of benefits of IIFE is to create local scope and this is so important if I have many js files that may have the same variable names. So IIFE avoid overriding and protect the scope of its variables.

There two way to call the function:

  • First, Our traditional way to define and call the function
function printName(){
  let myName = "Mohamed"
  console.log(myName)
}

//Invoke
printName()
Enter fullscreen mode Exit fullscreen mode
  • Second, Using IIFE. We wrap our function in brackets then we add a pair of brackets at the end of the function

(Our function)(Invoke)
(Our function)()

(function printName(){
  let myName = "Mohamed"
  console.log(myName)
})()
Enter fullscreen mode Exit fullscreen mode

In fact we don't need to give the function a name because its called only once. So IIFE will usually be anonymous function

(function(){
  let myName = "Mohamed"
  console.log(myName)
})()
Enter fullscreen mode Exit fullscreen mode

Closures

image

Closures is when a function remembers its lexical scope even when a function is executed outside the lexical scope. So closure is when a function use a variable defined in another function or another scope. So it make a link to this variable to update its value.

image

In the example, We have a printName function that have a variable. Then we have nested print function that use this variable in this scope. Then We have closure function that call the print function. Finally, We can call this function in another scope.
In other words, We can execute print function that use a name variable. This variable isn't declared here in closure function scope. But this variable is in printName function scope.
By default the logic is wrong. But in fact this is the closure and how it works. So if we change or update the value of our variable name, The closure will update it.

function printName(){
   var name="Mohamed";
   //name="Mohamed Khaled";
   function print() {
      console.log(name);
   }

   closure(print);
}

function closure(func)
{    
    func();
}

printName();
Enter fullscreen mode Exit fullscreen mode

Another example, We can get and update variable x in the inner function

function outer(){
  let x = 4
  function inner (){
    let y = x
    y = 16
    console.log(x)
    console.log(x*2)
    console.log(y)
  }

  closure(inner);  
}

function closure(inn)
{    
    inn();
}

outer()
Enter fullscreen mode Exit fullscreen mode

This is another way to create the previous closure function. Here's we have been replaced the inner function with anonymous function that return multiple values in an array. Then we executed the outer function.

function outer(){
  let x = 4
  return function (){
    let y = x
    y = 16
    return [x,x*2,y]
  }
}

//IIFE
console.log(outer()());

//let res = outer()
//console.log(res());
Enter fullscreen mode Exit fullscreen mode

let's look at another example, It is a simple counter using closure. Anyway, I recommend you to use ++n and see the difference.

function counter(n){
  return function (){
    return n++
  }
}

let res = counter(1)
console.log(res());
console.log(res());
console.log(res());
console.log(res());
console.log(res());
Enter fullscreen mode Exit fullscreen mode

let's dive into more complex. What do you expect the output of this code? THINK!!

for(var i=0;i<10;i++){
    setTimeout(function(){
        console.log(i);
    },100);
}
Enter fullscreen mode Exit fullscreen mode

After thinking. The output is the last value of our counter i. Which is 10.
Because i is a variable defined in the global scope. So this happened because of the closure. Again, Clousure use the last value of our variable that is defined in another scope.

I think you want to know how to solve this problem? Ok, There is more than one solution. One of them to use let to create the counter i because let is a local scope not global.

for(let i=0;i<10;i++){
    setTimeout(function(){
        console.log(i);
    },100);
}
Enter fullscreen mode Exit fullscreen mode

We can solve it using IIFE function which is executed immediately. So closure fixing setTimeout.

function closure (index){
    setTimeout(function(){
        console.log(index)
    },100)
}

for(var i=0;i<10;i++){
    (closure)(i)
}
Enter fullscreen mode Exit fullscreen mode

Synchronous vs Asynchronous

Synchronous programming

image
Synchronous programming means your code runs line by line, function by function. So you can't run two functions at the same time.

Asynchronous programming

image
Asynchronous function in three simple words means "it can wait". In another words means your function can be run while another function is running. So you can run two functions at the same time without freezing the program.

Asynchronous functions are coming from Web APIs that have a lot of asynchronous functions. JS has many of built in asynchronous function such as setTimeOut, setInterval, Promises, Event handlers and etc.

There's another type of function called Callback function which is executed after asynchronous function ends

In the next example, We’re defining a function getStudent that take a callback function as a parameter. Then we are calling the callback function that return the name and the age of student with delaying the response for 2 seconds.

Finally we call getStudent and pass the callback function as a parameter and this function is invoked when the 2 second delay is passed.

From the output, The last console.log statement is executed first because the execution of the callback function is still delayed by 2 second, so the the output is delayed.

const getStudent = callback => {
    setTimeout(() => {
       callback ({ name: 'Mohamed', age: 23 })
    }, 2000)
}

getStudent(student => {
    console.log("This is executed second")
    console.log(student.name, student.age)
})

console.log("This is executed first")
Enter fullscreen mode Exit fullscreen mode

Promises

image

What is promise?

Promise is a built in asynchronous function in JS which handling asynchronous code easier.
A promise is an asynchronous action that may complete at some point and produce a value. So with Promise, We try to run some of operations. If the operations succeeded to run, we make for the promise something called resolve. If there a failure, we make reject. So promise deals with Asynchronous operations.

How to create promise?

We use a constructor called Promise that takes an executor function. This function tries to run the operations and make resolve or reject for the promise.

This is first promise

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('promise done')
    resolve('done')
  }, 2000)
})
Enter fullscreen mode Exit fullscreen mode

How to know if the promise works or not?

To know the result of the promise is resolve or reject. We use then and catch to get the result.

  • Then takes a function runs successfully when occurs a resolve for promise. This means the action finished successfully. Also then returns another promise.
  • Catch takes a function runs successfully when occurs reject for promise or it failed.
let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('promise done')
    resolve('done')
    //reject('Is not done. Error')
  }, 2000)
})

p.then(() => console.log('promise resolved'))
 .catch(() => console.log('promise rejected'))
Enter fullscreen mode Exit fullscreen mode

The value for resolve or reject

Now, Whatever the result is resolve or reject. What if we need the value of this resolve or reject.
Here's our value for resolve is 'done' and our value fore reject is 'Is not done. Error'. So to get it, Our then or catch function takes a parameter.

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('promise done')
    resolve('Done')
    reject('Is not done. Error')
  }, 2000)
})

p.then((res) => console.log('promise resolved', res))
 .catch((err) => console.log('promise rejected', err))
Enter fullscreen mode Exit fullscreen mode

Nested promise

What if our promise ended and we want to run another promise. This is called nested promise.

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('promise done')
    resolve('Done')
  }, 2000)
})

//Nested promise
p.then((res) => {
  p.then(res2 => console.log(res2))
})
Enter fullscreen mode Exit fullscreen mode

Chaining promise

I want to tell you that nested promise isn't good practice. So there chaining promise.
Here's our function return our promise p and the result of then function is our promise p. Finally we can use then to make chaining promise.

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('promise done')
    resolve('Done')
  }, 2000)
})

//Chaining promise 
p.then((res) => {
  return p
}).then(res2 => console.log(res2))
//p.then((res) => p).then(res2 => console.log(res2))
Enter fullscreen mode Exit fullscreen mode

Here's the final code

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('promise done')
    resolve('Done')
  }, 2000)
})

//Nested promise
p.then((res) => {
  p.then(res2 => console.log(res2))
})

//Chaining promise 
p.then((res) => {
  return p
}).then(res2 => console.log(res2))

//Chaining promise 
p.then((res) => p).then(res2 => console.log(res2))

//Chaining promise .. Best practice and more readable
p
  .then((res) => p)
  .then(res2 => console.log(res2))
Enter fullscreen mode Exit fullscreen mode

When I have chaining promise. If any promise is rejected, It will run the first catch and ignore the rest.

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('promise done')
    reject('Is not done. Error')
  }, 2000)
})

//Chaining promise 
p
  .then((res) => p)
  .then(res2 => console.log(res2))
  .catch((err1) => console.log('promise rejected 1', err1))
  .catch((err2) => console.log('promise rejected 2', err2))
Enter fullscreen mode Exit fullscreen mode

Finally, Do you remember our callback example. I am going to make it with promise with the same output. Try to understand it LOL :)

let p = new Promise((resolve, reject) => { setTimeout(() => { let error = false; if(error) { console.log("This is executed second, Done") resolve({ name: 'Mohamed', age: 23 }) } else { console.log("This is executed second, Error") reject("Error404") } }, 2000) }) const getStudent = () => { return p } getStudent() .then(student => { console.log(student.name, student.age)}) .catch(err => console.log('promise rejected', err)) console.log("This is executed first")

Async vs Await

Async

Async is keyword, Await is operator. They have been added to ES8.
Async vs Await make us deal with promise in a way better than using promise chain so our promise became more easier.

Async used with a function that means this is asynchronous function but it return a promise.

  • Return == Resolve
  • Throw == Reject

In this example, Our promise resolve or return myName 'I am Mohamed'

async function myName(){
   return 'I am Mohamed'
}

myName().then( msg => console.log(msg))
Enter fullscreen mode Exit fullscreen mode

In this example, Our promise reject or throw isName 'Is not Mohamed'

async function isName(){
   throw 'Is not Mohamed'
}
isName().catch( msg => console.log(msg))
Enter fullscreen mode Exit fullscreen mode

await

await means you have to wait until you execute this line. await is only valid in async function.

In the next example, We have promise p and async function called myName. We will notice that 'This is executed first' is first line but then we have to wait until our promise p ends. Finally after the promise done, The rest is executed so last line is 'I am Mohamed'.

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('promise done')
    resolve('Done')
  }, 2000)
})

async function myName(){
   console.log('This is executed first')
   await p
   //p
   console.log('I am Mohamed')
}

myName()
Enter fullscreen mode Exit fullscreen mode

Another example

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('promise done')
    resolve('Done')
  }, 2000)
})

async function myName(){
   console.log('This is executed first')
   await p

   console.log('I am Mohamed')

   setTimeout(() => {
    console.log('Last line')
  }, 5000)

   console.log('I am Egyptian')
}

myName()
Enter fullscreen mode Exit fullscreen mode

You know promise make resolve or reject. Now, The result of await is the result of resolve or reject.

If the promise make resolve

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('promise done')
    resolve('Done')
  }, 2000)
})

async function myName(){
   let result = await p
   console.log('The result of await is : ' + result)
}
myName()
Enter fullscreen mode Exit fullscreen mode

If the promise make reject, It automatic throw the error. So we have to avoid promise chain and use this way.

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('promise done')
    //resolve('Done')
    reject('error 404')
  }, 2000)
})

async function myName(){
   let result = await p
   return result
}

myName()
  .then( res => console.log('The result of await is : ' + res))
  .catch( err => console.log('Error: ' + err))
Enter fullscreen mode Exit fullscreen mode

Finally, Do you remember our callback example. We did it in two ways using callback and promise.
Now, I am going to make it using async and await with the same output. Try to understand it again on your own :) LOL :(

let p = new Promise((resolve, reject) => { setTimeout(() => { resolve({ name: 'Mohamed', age: 23 }) //reject('error 404') }, 2000) }) const getStudent = () => { return p } async function fetchStudent () { let student = await getStudent() return student } fetchStudent() .then(student => console.log(student.name + " " + student.age)) .catch((err) => console.log("Error: " + err)) console.log("This is executed first")
  • Also we can use try and catch to handle error
let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        let error = false;
        if(error)
        {
            console.log("This is executed second, Done")
            resolve({ name: 'Mohamed', age: 23 })
        }
        else
        {
            console.log("This is executed second, Error")
            reject()
        }
    }, 2000)
})

const getStudent = () => {
    return p
}

async function fetchStudent () {
    try { 
        const student = await getStudent()
        return student
    } catch (error) {
        console.log("Error")
    }
}

fetchStudent()
  .then(student => console.log(student.name + " " + student.age))
  .catch(() => console.log("error 404"))

console.log("This is executed first")
Enter fullscreen mode Exit fullscreen mode

Conclusion

Thank you for reading and I hope you have found valuable information here.
Here is the repo, You can find the source code and feel free to fork it.

Top comments (0)