Promise Basics

Introduction

A Promise is a JavaScript object that represents the eventual completion or failure of an asynchronous operation. Instead of using callback functions to handle asynchronous tasks, Promises provide a cleaner and more organized way to write asynchronous code.

Promises were introduced in JavaScript to solve problems such as callback hell, where multiple nested callback functions make code difficult to read and maintain.

Node.js uses Promises extensively for operations like reading files, making HTTP requests, querying databases, and interacting with external services. Modern automation frameworks such as Playwright, Puppeteer, WebdriverIO, and many Node.js libraries are built around Promises.

For automation engineers, understanding Promises is essential because most browser automation, API testing, file handling, and database operations return Promises.

In this tutorial, you’ll learn the basics of Promises, how they work, and how to use them in Node.js.


What is a Promise?

A Promise is an object that represents the future result of an asynchronous operation.

A Promise eventually produces either:

  • A successful result (fulfilled)

  • An error (rejected)

Until one of these occurs, the Promise remains pending.


Why Use Promises?

Promises help developers:

  • Write cleaner asynchronous code.

  • Avoid callback hell.

  • Improve readability.

  • Handle errors more effectively.

  • Chain asynchronous operations.

  • Improve code maintainability.

  • Simplify asynchronous workflows.


Promise States

Every Promise has one of the following states:

StateDescription
PendingInitial state. The operation is still running.
FulfilledThe operation completed successfully.
RejectedThe operation failed with an error.

Once a Promise becomes fulfilled or rejected, its state cannot be changed.


Creating a Promise

A Promise is created using the Promise constructor.

Syntax

const promise =
    new Promise(function (
        resolve,
        reject
    ) {

        resolve("Success");

    });
  • resolve() is called when the operation succeeds.

  • reject() is called when the operation fails.


Example 1: Successful Promise

const promise =
    new Promise(function (
        resolve,
        reject
    ) {

        resolve(
            "Operation completed."
        );

    });

promise.then(function (result) {

    console.log(result);

});

Sample Output

Operation completed.

Example 2: Rejected Promise

const promise =
    new Promise(function (
        resolve,
        reject
    ) {

        reject(
            "Something went wrong."
        );

    });

promise.catch(function (error) {

    console.log(error);

});

Sample Output

Something went wrong.

Example 3: Promise with setTimeout()

const promise =
    new Promise(function (
        resolve
    ) {

        setTimeout(function () {

            resolve(
                "Task completed."
            );

        }, 2000);

    });

promise.then(function (result) {

    console.log(result);

});

Sample Output

Task completed.

Example 4: Promise with Condition

const age = 20;

const promise =
    new Promise(function (
        resolve,
        reject
    ) {

        if (age >= 18) {

            resolve(
                "Eligible."
            );

        } else {

            reject(
                "Not eligible."
            );

        }

    });

promise
.then(function (result) {

    console.log(result);

})
.catch(function (error) {

    console.log(error);

});

Sample Output

Eligible.

Example 5: Reading a File with Promises

const fs =
    require("fs").promises;

fs.readFile(
    "sample.txt",
    "utf8"
)
.then(function (data) {

    console.log(data);

})
.catch(function (error) {

    console.log(error);

});

The file is read asynchronously, and the result is handled using a Promise.


Automation Testing Examples

Promises are used extensively in automation frameworks.

Playwright Example

Simulate page loading.

const pageLoad =
    new Promise(function (resolve) {

        resolve(
            "Page loaded."
        );

    });

pageLoad.then(function (message) {

    console.log(message);

});

Sample Output

Page loaded.

Selenium Example

Simulate browser startup.

const browser =
    new Promise(function (resolve) {

        resolve(
            "Browser started."
        );

    });

browser.then(function (message) {

    console.log(message);

});

Sample Output

Browser started.

Cypress Example

Simulate application launch.

const application =
    new Promise(function (resolve) {

        resolve(
            "Application opened."
        );

    });

application.then(function (message) {

    console.log(message);

});

Sample Output

Application opened.

API Testing Example

Handle an API response.

const api =
    new Promise(function (resolve) {

        resolve(
            "API response received."
        );

    });

api.then(function (message) {

    console.log(message);

});

Sample Output

API response received.

Data-Driven Testing Example

Load test data.

const data =
    new Promise(function (resolve) {

        resolve(
            "CSV file loaded."
        );

    });

data.then(function (message) {

    console.log(message);

});

Sample Output

CSV file loaded.

Promise vs Callback

FeatureCallbackPromise
ReadabilityLowerHigher
Callback HellPossibleAvoided
Error HandlingManualCentralized with .catch()
ChainingDifficultEasy with .then()
Modern JavaScriptLess preferredPreferred

Common Mistakes

Forgetting to Handle Errors

Always use .catch() to handle rejected Promises.


Mixing Callbacks and Promises

Avoid combining callback-based and Promise-based code unless necessary.


Not Returning Promises

When chaining operations, return Promises to ensure proper execution order.


Best Practices

  • Use Promises for asynchronous operations.

  • Always handle errors with .catch().

  • Keep Promise chains simple.

  • Return Promises from functions.

  • Avoid unnecessary nested Promises.

  • Use meaningful variable names.

  • Prefer async/await for complex asynchronous workflows.


Conclusion

Promises provide a modern and structured way to manage asynchronous operations in JavaScript. They improve readability, simplify error handling, and eliminate many of the problems associated with callback-based programming.

For automation engineers, Promises are fundamental because modern Node.js libraries and automation frameworks rely heavily on them for browser automation, API testing, file handling, and database interactions. Mastering Promise basics will prepare you for learning Promise chaining, Promise.all(), and async/await.


Frequently Asked Questions (FAQs)

What is a Promise?

A Promise is an object that represents the eventual result of an asynchronous operation.


What are the three Promise states?

Pending, Fulfilled, and Rejected.


What is resolve()?

resolve() marks a Promise as successfully completed.


What is reject()?

reject() marks a Promise as failed and passes an error.


Why are Promises important in automation testing?

Modern automation frameworks use Promises to manage asynchronous operations such as browser interactions, API requests, file handling, and database operations.


Key Takeaways

  • Promises represent future results of asynchronous operations.

  • Every Promise has three states: Pending, Fulfilled, and Rejected.

  • Use resolve() for success and reject() for failure.

  • Handle successful results using .then().

  • Handle errors using .catch().

  • Promises improve code readability and maintainability.

  • They eliminate many callback-related problems.

  • Modern Node.js libraries rely heavily on Promises.

  • Automation frameworks extensively use Promises.

  • Understanding Promise basics is essential before learning Promise chaining and async/await.