DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Utilizing Database Hooks Like a Pro in Node.js
  • My 7 Must-Have Tools for JavaScript Pros That I Can’t Live Without in 2023
  • A Beginner's Guide to Back-End Development
  • DZone Community Awards 2022

Trending

  • Create Your Own AI-Powered Virtual Tutor: An Easy Tutorial
  • Java Virtual Threads and Scaling
  • Debugging Core Dump Files on Linux - A Detailed Guide
  • Problems With Angular Migration
  1. DZone
  2. Data Engineering
  3. Databases
  4. Asynchronous Programming

Asynchronous Programming

If you develop JavaScript in Node or the browser, it's asynchronous. Read on to learn how to work around the little quirks that pop up as a result.

By 
James Warden user avatar
James Warden
·
Nov. 09, 17 · Tutorial
Likes (13)
Comment
Save
Tweet
Share
21.2K Views

Join the DZone community and get the full member experience.

Join For Free

Introduction

JavaScript is an asynchronous programming language in Node and in the browser. In many languages such as Java, C#, Python, etc. they block the thread for I/O. What this means is when you make an HTTP/ajax call or read a text file, for example, the runtime will pause on that line of code until it is successful or a failure.

JavaScript does the opposite. Using callbacks or Promises, you basically leave a phone number to call when those operations are done, while the rest of the synchronous code keeps going. In this article, we'll talk about why, give examples from JavaScript and compare against a blocking language, and show you some tips to help.

Why?

The V8 engine that runs in the Chrome web browser and Node ensures a good user experience. This means, whenever you request a URL, load an image, etc. it doesn't block the UI thread. The user can still interact with the web page while those asynchronous operations are going on. If it worked like blocking languages, the web page would lock up as things load, and buttons would only occasionally be clickable, text fields would stop working while you type and they run validation code, etc.

When you're writing imperative code, it's important you understand how this works so you don't create hard to debug race conditions.

Real World Examples

Here's an example of querying an Oracle database in Python:

connection = cx_Oracle.connect("hr", "welcome", "localhost/orclpdb")
cursor = connection.cursor()
cursor.execute(""
    "
    SELECT first_name, last_name FROM employees WHERE department_id = : did AND employee_id > : eid ""
    ",
    did = 50,
    eid = 190)
for fname, lname in cursor:
    print("Values:", fname, lname)

Whether the first line that has the connect function takes 1 nanosecond or 1 minute, no other lines of code will run. The program will chill out until connect returns, and if successfully connected, then it'll run line 2 to get the cursor from the connection. Connect, get cursor, execute the query, then print the results.

Here's a broken example written in JavaScript:

var connection;
oracledb.getConnection({
    user: "hr",
    password: "welcome",
    connectString: "localhost/orclpdb"
}, (err, conn) {
    if (err) {
        throw err;
        return;
    }
    connection = conn;
});
connection.execute(
`FROM employees
WHERE department_id = :did AND employee_id > :eid`, [50, 190],
(err, result) {
    if (err) {
        throw err;
        return;
    }
    console.log("Values:", result.rows);
});
});

We attempt to connect, and once the callback is called, we set the connection variable on line 7 so we can re-use it later to execute the query.

Programmers from blocking languages might think the program will wait for the connection callback to fire before proceeding to the next. That's not how it works. The worst part is that some implementations of callbacks, if they are fast enough, could make this work. Horrible race conditions that are hard to debug follow.

How Does It Work? 

Let's use a simple example in Python that prints out strings, in order, the code runs:

from __future__
import print_function
from time
import sleep
def fast(): print('1')
def slow(callback):
    sleep(1)
print('2')
callback()
fast()
slow(lambda: print('3'))
print('4')

Python will print out 1, 2, 3, 4. The fast function is pretty straightforward in most programming languages. Slow uses the sleep function to pause. It works like all other I/O functions in that it takes awhile and pauses the execution of your code on that line. Then it executes the callback which is a lambda function below. Finally 4. Pretty imperative; line by line.

Here's the same example in JavaScript:

fast = () => console.log('1');
slow = callback =>
    setTimeout(() => {
        console.log('2');
        callback();
    }, 100);
fast();
slow(() => console.log('3'));
console.log('4');

Node prints out 1, 4, 2, 3. What?!

The fast function makes sense. However, slow operates differently than Python. JavaScript never waits ( alert doesn't count). With any asynchronous calls, JavaScript will assume a callback will be passed in, or a Promise will be returned. When whatever long running task is done, that callback or Promise will be called. So the console.log('4') gets run immediately after slow.

Promises work in a similiar manner:

fast = () => console.log('1');
slow = () => new Promise(success => {
    console.log('2')
    success();
});
fast();
slow().then(() => console.log('3'));
console.log('4');

It'll print out 1, 4, 2, 3, same as before. The then or catch will fire whenever the long-running process is done.

Callback vs. Promises

If you wish to skip nerdy nuances, head on down to the Conclusions.

There is one nuance of race conditions that callbacks can cause that Promises avoid, yet still have problems of their own.

Callback Race Condition

Let's revisit our callback example above:

fast = () => console.log('1');
slow = callback =>
    setTimeout(() => {
        console.log('2');
        callback();
    }, 100);
fast();
slow(() => console.log('3'));
console.log('4');

It prints out 1, 4, 2, 3. Let's remove the setTimeout and have the callback immediately invoke:

fast = () => console.log('1');
slow = callback => {
    console.log('2');
    callback();
};
fast();
slow(() => console.log('3'));
console.log('4');

Oh boy, it prints out 1, 2, 3, 4. Depending on "how fast" your code runs, especially when unit testing with mocks, you now have a race condition. This is why a lot of implementations that use callbacks will often use defer functions internally to ensure the code always runs in the same order (i.e. waiting till the current call stack clears).

Promise Race Condition

Let's revisit our Promise implementation:

fast = () => console.log('1');
slow = () => new Promise(success => {
    setTimeout(() => {
        console.log('2');
        success();
    }, 100);
});
fast();
slow().then(() => console.log('3'));
console.log('4');

It prints out 1, 4, 2, 3. Let's remove the setTimeout and see what it what happens:

fast = () => console.log('1');
slow = () => new Promise(success => {
    console.log('2');
    success();
});
fast();
slow().then(() => console.log('3'));
console.log('4');

It prints out 1, 2, 4, 3. So bad news and good news. The good news, the asynchronous response still fires after your main code has run ( console.log('4')). The bad news is the contents of the Promise itself will run. It looks like when you call the success, like the callback, it should be immediately calling your success which would fire the then, but it's not. Like the defer, the Promise internally will wait until the current call stack has cleared, then running all pending successes or failures. More strange things for your brain to keep track of.

This can get imperative coders in trouble.

Async/Await

Despite the challenges with Promises, there are ways to make them operate more imperatively, and help prevent the race conditions that can occur for programmers who either haven't spent enough time with Promises and/or are from other languages where they already have this functionality such as C#.

Modifying our example above:

fast = () => console.log('1');
slow = () => new Promise(success => {
    setTimeout(() => {
        console.log('2');
        success('slow is done');
    }, 100);
});
const go = async () => {
    fast();
    result = await slow();
    console.log('3, result:', result);
    console.log('4');
};
go();

It prints out 1, 2, 3, result: slow is done, 4. So you can see the code DID appear to "stop and wait" on the result = await slow() line. This can make your code more readable for async code you want to write in a synchronous manner.

Sadly, the error handling is gone, and you must wrap those blocks in try/catch. It's a trade-off. You can ensure this is less of a problem by ensuring all Promises you create never fire catch, and the success method returns an Either.

Conclusion

As you can see, JavaScript is an asynchronous language by default. While many things still use callbacks, it's better to use Promises. Even doing so, you can still create race conditions. Ensuring you chain them together, and/or utilize new functionality like async/await can help mitigate it. This is also why using const as opposed to var and let can force you to ensure your variables are created at the right time.

To help yourself remember, think "I'll call you back later." For Promise, think "I promise to call then or catch you later."

Database JavaScript

Published at DZone with permission of James Warden, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Utilizing Database Hooks Like a Pro in Node.js
  • My 7 Must-Have Tools for JavaScript Pros That I Can’t Live Without in 2023
  • A Beginner's Guide to Back-End Development
  • DZone Community Awards 2022

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!