Promise in Javascript at Five Levels

Let’s dive into the concept of Promises in JavaScript:

1. To a Child:

A Promise in JavaScript is like when your parents promise to take you to the park. Until they do, you’re in a state of waiting. They could fulfill the promise and take you to the park, or something could come up and they might have to break the promise. In JavaScript, a Promise is a way for the program to say “I promise to do this task, but I’m not sure how long it will take.”

2. To a Teenager:

Promises in JavaScript are like ordering something online. When you order, you get a tracking number (the Promise), and you wait for the package to arrive. It might arrive as expected (resolved), it might arrive late (still pending), or it might not arrive at all due to some issue (rejected). A Promise in JavaScript is a way for the program to deal with tasks that take time, like loading data from a server.

3. To an Undergraduate Computer Science Major:

A Promise in JavaScript represents an operation that isn’t completed yet, but is expected in the future. It has three states: pending, fulfilled, and rejected. Promises are used for asynchronous operations, like network requests, where you don’t know when the operation will complete. Once a Promise is settled (either fulfilled or rejected), it cannot change its state or value.

4. To a Graduate Computer Science Student:

Promises in JavaScript provide a way to handle the eventual completion or failure of an asynchronous operation, and its resulting value. A Promise is an object representing the eventual completion or failure of an async operation. It’s a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers to an asynchronous action’s eventual success value or failure reason.

5. To a Colleague (Professional Developer):

A Promise in JavaScript is an object that links the “producing code” and the “consuming code” together. In other words, this is a container for an asynchronous operation. The constructor syntax for a promise object is: new Promise(executor), where executor is a function with two parameters, resolve and reject. These functions are pre-defined by the JavaScript engine. Promises give us a way to handle asynchronous processing in a more synchronous fashion by representing a value that may not be available yet, but will be resolved or rejected at some point in the future.

Code

Promises provide a new way of structuring callback functions. If used correctly (and unfortunately, Promises are easy to use incorrectly), they can convert asyn‐ chronous code that would have been nested into linear chains of then() calls where one asynchronous step of a computation follows another. Also, Promises allow you to centralize your error-handling code into a single catch() call at the end of a chain of then() calls.

Let’s break it down and create a simple example using Promises.

Imagine you want to read a file from disk and then upload that file content to a server. Both operations (reading from disk and uploading to a server) are asynchronous tasks.

Without Promises, you might structure your code using callbacks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
const fs = require('fs');
const request = require('request');

fs.readFile('file.txt', 'utf8', function(err, data) {
    if (err) {
        console.error('Failed to read file', err);
        return;
    }
    
    request.post('http://mywebsite.com/upload', { body: data }, function(err, res) {
        if (err) {
            console.error('Failed to upload', err);
            return;
        }
        
        console.log('Upload successful!');
    });
});

This nested structure can become difficult to read and manage as the number of asynchronous steps increases. Now, let’s restructure this code using Promises to create a linear flow:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const fs = require('fs').promises;
const request = require('request-promise');  // This version returns a Promise

fs.readFile('file.txt', 'utf8')
    .then(data => {
        return request.post('http://mywebsite.com/upload', { body: data });
    })
    .then(() => {
        console.log('Upload successful!');
    })
    .catch(err => {
        console.error('An error occurred', err);
    });

In the Promise-based example, the code is much easier to follow. Each asynchronous step follows the previous one in a linear sequence. Furthermore, all error handling is centralized in the single catch() block at the end.

This example assumes the usage of request-promise library for request and the promise-based fs (fs.promises) that Node.js has added recently. Note that not all libraries return Promises, so in some cases you may need to use utility functions to convert callback-based functions into Promise-based ones, or use libraries that support Promises out of the box.