Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Building Data-Driven Applications with Danfo.js

You're reading from   Building Data-Driven Applications with Danfo.js A practical guide to data analysis and machine learning using JavaScript

Arrow left icon
Product type Paperback
Published in Sep 2021
Publisher Packt
ISBN-13 9781801070850
Length 476 pages
Edition 1st Edition
Languages
Arrow right icon
Authors (2):
Arrow left icon
Stephen Oni Stephen Oni
Author Profile Icon Stephen Oni
Stephen Oni
Rising Odegua Rising Odegua
Author Profile Icon Rising Odegua
Rising Odegua
Arrow right icon
View More author details
Toc

Table of Contents (18) Chapters Close

Preface 1. Section 1: The Basics
2. Chapter 1: An Overview of Modern JavaScript FREE CHAPTER 3. Section 2: Data Analysis and Manipulation with Danfo.js and Dnotebook
4. Chapter 2: Dnotebook - An Interactive Computing Environment for JavaScript 5. Chapter 3: Getting Started with Danfo.js 6. Chapter 4: Data Analysis, Wrangling, and Transformation 7. Chapter 5: Data Visualization with Plotly.js 8. Chapter 6: Data Visualization with Danfo.js 9. Chapter 7: Data Aggregation and Group Operations 10. Section 3: Building Data-Driven Applications
11. Chapter 8: Creating a No-Code Data Analysis/Handling System 12. Chapter 9: Basics of Machine Learning 13. Chapter 10: Introduction to TensorFlow.js 14. Chapter 11: Building a Recommendation System with Danfo.js and TensorFlow.js 15. Chapter 12: Building a Twitter Analysis Dashboard 16. Chapter 13: Appendix: Essential JavaScript Concepts 17. Other Books You May Enjoy

Promises and async/await

Let's dive a bit into the world of Asynchronous functions, functions that we call now but finish later. In this section, we will see why we need Promise and async/await.

Let's start with a simple problem as shown in the following code snippet. We are given a task to update an array with a function, after 1 second of calling the function:

let syncarray = ["1", "2", "3", "4", "5"]
function addB() {
    setTimeout(() => {
        syncarray.forEach((value, index)=>{
            syncarray[index] = value + "+B"
        })
        console.log("done running")
    }, 1000)
}
addB()
console.log(syncarray);
// output 
// ["1", "2", "3", "4", "5"]
// "done running"

console.log(syncarray) is executed before the addB() function, hence we see the syncarray output before it is being updated. This is an Asynchronous behavior. One of the ways to solve this is to use a callback:

let syncarray = ["1", "2", "3", "4", "5"]
function addB(callback) {
    setTimeout(() => {
        syncarray.forEach((value, index)=>{
            syncarray[index] = value + "+B"
        })
        callback() //call the callback function here
    }, 1000)
}
addB(()=>{
  // here we can do anything with the updated syncarray 
  console.log(syncarray);  
})
// output 
// [ '1+B', '2+B', '2+B', '4+B', '5+B' ]

Using the preceding callback approach means that we always pass in callbacks in order to perform other operations on the updated syncarray function. Let's update the code a little, and this time we'll also add the string "A" to syncarray and then print out the updated array:

let syncarray = ["1", "2", "3", "4", "5"]
function addB(callback) {
    setTimeout(() => {
        syncarray.forEach((value, index) => {
            syncarray[index] = value + "+B"
        })
        callback() //call the callback function here
    }, 1000)
}
addB(() => {
    setTimeout(() => {
        syncarray.forEach((value, index) => {
            syncarray[index] = value + "+A";
        })
        console.log(syncarray);
    }, 1000)
})
// output
// [ '1+B+A', '2+B+A', '3+B+A', '4+B+A', '5+B+A' ]

The preceding code block shows a quick way of passing callback. Based on the arrow function we discussed, it can be more organized by creating a named function.

Cleaning callbacks with promises

Using callbacks quickly becomes unwieldy and can quickly descend into callback hell. One method of freeing ourselves from this is to make use of Promises. Promises makes our callbacks more organized. It gives a chainable mechanism to unify and orchestrate code that is dependent on previous functions, as you'll see in the following code block:

let syncarray = ["1", "2", "3", "4", "5"]
function addA(callback) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            syncarray.forEach((value, index) => {
                syncarray[index] = value + "+A";
            })
            resolve()
        }, 1000);
    })
}
addA().then(() => console.log(syncarray)); 
//output
//[ '1+A', '2+A', '2+A', '4+A', '5+A' ]

In the preceding code snippet, setTimeout is wrapped inside the Promise function. A Promise is always instantiated using the following expression:

New Promise((resolve, rejection) => {
})

A Promise is either resolved or rejected. When it is resolved, then we are free to do other things, and when it is rejected, we need to handle the error.

For example, let's ensure that the following Promise is rejected:

let syncarray = ["1", "2", "3", "4", "5"]
function addA(callback) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            syncarray.forEach((value, index) => {
                syncarray[index] = value + "+A";
            })
            let error = true;
            if (error) {
                reject("just testing promise rejection")
            }
        }, 1000);
    })
}
addA().catch(e => console.log(e)) // just testing promise rejection

And whenever we have multiple promises, we can use the .then() method to handle each one:

addA.then(doB)
     .then(doC)
     .then(doD)
     .then(doF)
     .catch(e= > console.log(e));

The use of multiple .then() methods to handle numerous promises can quickly become unwieldy. To prevent this, we can use methods such as Promise.all(), Promise.any(), and Promise.race().

The Promise.all() method takes in an array of promises to be executed, and will only resolve when all promises are fulfilled. In the following code snippet, we add another Asynchronous function to our previous example and use Promise.all() to handle them:

let syncarray = ["1", "2", "2", "4", "5"]
function addA() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            syncarray.forEach((value, index) => {
                syncarray[index] = value + "+A";
            })
            resolve()
        }, 1000);
    })
}
function addB() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            syncarray.forEach((value, index) => {
                syncarray[index] = value + "+B";
            })
            resolve()
        }, 2000);
    })
}
Promise.all([addA(), addB()])
.then(() => console.log(syncarray)); // [ '1+A+B', '2+A+B', '2+A+B', '4+A+B', '5+A+B' ]

From the output in the preceding section, you can see that each Asynchronous function gets executed in the order it was added, and the final result is the effect of both functions on the syncarray variable.

The promise.race method, on the other hand, will return as soon as any promise in the array is resolved or rejected. You can think of this as a race where each promise tries to resolve or reject first, and as soon as this happens, the race is over. To see an in-depth explanation as well as code examples, you can visit the MDN docs here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any.

And finally, the promise.any method will return on the first fulfilled promise irrespective of any other rejected promise function. If all promises are rejected, then Promise.any rejects promises by providing errors for all of them. To see an in-depth explanation as well as code examples, you can visit the MDN docs here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race.

While using promises to work with callback solves a lot of issues, there is an even better way of implementing or using them. These are called async/await functions. We'll introduce these functions and show you how to use them in the following section.

async/await

As said earlier, async/await provides a more elegant way of working with promises. It gives us the power to control how and when each promise function gets called inside a function, instead of using .then() and Promise.all().

The following code snippet shows how you can use async/await in your code:

Async function anyName() {
    await anyPromiseFunction()
         await anyPromiseFunction()
}

The preceding async function can contain as many promise functions as possible, each waiting for the other to execute before being executed. Also, note that an async function is resolved as a Promise. that is, you can only obtain the return variable of the preceding anyName function (or resolve the function) using .then() or by calling it in another async/await function:

Async function someFunction() {
    await anyPromiseFunction()
         await anotherPromiseFunction()
    return "done"
}
// To get the returned value, we can use .then()
anyName().then(value => console.log(value)) // "done"
// we can also call the function inside another Async/await function
Async function resolveAnyName() {
   const result = await anyName()
   console.log(result)
}
resolveAnyName() // "done"

With this knowledge, here is how we can rewrite the promise execution from the previous section instead of using Promise.all([addA(), addB()]):

let syncarray = ["1", "2", "2", "4", "5"] 
function addA(callback) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            syncarray.forEach((value, index) => {
                syncarray[index] = value + "+A";
            })
            resolve()
        }, 1000);
    })
}
function addB(callback) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            syncarray.forEach((value, index) => {
                syncarray[index] = value + "+B";
            })
            resolve()
        }, 2000);
    })
}
Async function runPromises(){ 
    await addA() 
    await  addB() 
    console.log(syncarray); 
  } 
runPromises() 
//output: [ '1+A+B', '2+A+B', '2+A+B', '4+A+B', '5+A+B' ]

You can see from the preceding output that we have the same output as when using the Promise.all syntax, but are adopting a minimal and cleaner approach.

Note

One drawback of using multiple awaits as opposed to promise.all is efficiency. Though minor, promise.all is the preferred and recommended way to handle multiple independent promises.

This thread (https://stackoverflow.com/questions/45285129/any-difference-between-await-promise-all-and-multiple-await) on Stack Overflow clearly explains why this is the recommended way to handle multiple promises.

In the next section, we'll discuss object-oriented programming (OOP) in JavaScript, and how to use ES6 classes.

You have been reading a chapter from
Building Data-Driven Applications with Danfo.js
Published in: Sep 2021
Publisher: Packt
ISBN-13: 9781801070850
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime