DEV Community

Tim Conroy
Tim Conroy

Posted on • Updated on

The JavaScript Promise | Simplified

What is the JavaScript Promise?

Let's start at the base level.

  • 1. The top-level "[Promise]" is a JS built-in (native) object.
  • 2. It is a 'constructor function' which, like most objects in JS, has a prototype (object) property on it which points to an object. This object contains a "template" or "bucket" of properties and values(key/value pairs). One of those properties is a constructor whose value references the Promise constructor 'function' itself. The Promise's prototype object (Promise.prototype) can be accessed/referenced by newly created instance(child)objects of the parent object, Promise.
  • 3. The JavaScript Promise allows one to handle asynchronous operations in an otherwise synchronous, single-threaded(top-down), and non-blocking environment.

Putting it all together: A Promise is an object, which is a constructor function, that acts like a wrapper around other functions, whose purpose is to 'promise' a return value not yet known, in an asynchronous environment.

A Promise has three states:

  • 1. Pending: The inital state, it's neither fulfilled, nor rejected.
  • 2. Fulfilled: Successful.
  • 3. Rejected: meaning that the operation failed.

The JS Promise syntax: Promise(executor)

When a 'new Promise()' is invoked by the JS Engine, it creates a new Promise{} object, and the 'executor' is run automatically. The executor has two arguments which are 'resolve' and 'reject'. These are callback functions. The executor function first does it's job and obtains the result, be it sooner or later, it then calls on one of these callbacks depending on the executors' result.

The 'executor' is a function which takes two arguments, it's parameters are;

  1. Parameter one: 'resolve' resolve(value) : value is either (1) state: fulfilled, or (2)result: value.
  2. Parameter two: 'reject' reject(error) : state: 'rejected', or result: error.

The ES5 function declaration:

Promise(function(resolve,reject){};

The ES6 Arrow Function declaration:
Promise((resolve,reject) => {
resolve( //resolve someThing placed in here. );
reject(// if not resolved; do this.);
});

The 'executor function' is invoked immediately even before the 'Promise' constructor returns the instance object. (This is a [Promise] built-in functionality)

The parameter's 'resolve' and 'reject' are ordered inside the parentheses. So, the first parameter tests for 'truthy'. And, the second parameter tests for 'falsy'.

The parameters can be changed to any wording you like; however, conventional useage is encouraged by using 'resolve' and 'reject'.

The constructor syntax for creating a new promise object.

  • 1. Create a variable using either the let or const statement which declares a block-scoped variable. The difference in the two keywords is that the 'value' of const cannot be changed or re-declared. The const value can however be 'mutated. This can be rectified by using the ES6 Object.freeze() method.
let x = 5;  //globally scoped

    function foo(){
        let x = 'I am x!, scoped local to this block';  //locally scoped
        console.log(x);  // 'I am x!, scoped local to this block'
     };
console.log(x) // 5
foo();  // 'I am x!, scoped local to this block'
Enter fullscreen mode Exit fullscreen mode
  • 2. Using 'new Promise()' creates an instance object of the [Promise] object.

a. The new operator will create a blank object: Promise{}.
b. It links up(sets) the constructor of one object
(Promise.prototype.constructor) to another
object's constructor(the newly created instance object (p) of Promise;
p. __ proto __.constructor.)
c. It passes the newly created object (Promise {}) as the 'this'
context. For instance when creating(defining) properties in the
constructor function Promise's code block, we use the this keyword to
point to the parent object 'Promise' as we instantiate the property
members.

  • 3. The constructor function Promise and, resolve and reject.

A simplified use of a Promise for the purpose of explaining the resolve and reject methods (functions).

let p1 = new Promise((resolve, reject) => {

         //set x to the sum of two numbers.
          let x = 10 + 5;

         //if statement and strict equality operator
           if(x === 15) {
                resolve('x is 15');
           }else{
                reject('error: x is not 15')
           }
});
Enter fullscreen mode Exit fullscreen mode
    1. Now we 'use' the newly created instance (child) object, p1. We use the Promise.prototype.then() method(function) to return a [Promise].

    The '.then() method takes up to two arguments that are 'callback
    functions' for resolve(success) or the rejected(failure) methods.

The MDN syntax for the Promise.prototype.then() method is;

p.then(onFulFilled [, onRejected]);

p.then(value => {
    // fulfillment
}, reason => {
    // rejection
});
Enter fullscreen mode Exit fullscreen mode
  • Using an 'asynchronous' operation with a new promise.
let p2 = new Promise((resolve, reject) => { 

    //creating a 'asyn' condition for test purposes
    setTimeout(() => {
        resolve('I have been resolved!');
        reject('Failed.');
     }, 3000)  //3000 milliseconds or 3 seconds
});
Enter fullscreen mode Exit fullscreen mode

Now, let's test it!

p2.then((val) => {
  console.log(val)   
},(error) => {
  console.log(error);
});
Enter fullscreen mode Exit fullscreen mode

When the code is invoked there is a lot initially going on under the hood, but I want to try to keep my explanation short for the code snippet above.

  1. The let variable 'p2' is placed in global memory by the JS engine.
  2. The JS engine continues to parse the same line of code and sets the 'constructor function' identifier(name) 'Promise' with 'new' in global memory and as a copy with it's function definition and it's two parameters, resolve and reject. (The engine does not do anything but record the inner function definition, the setTimeout() method.)
  3. The JS engine next reads the 'p2' and trys to find it. It does. Then the JS engine sees the assignment operator (=) and knows that there is a right-operand. It continues on parsing the right operand; the new Promise(). It finds 'Promise' in it's global memory. The parentheses after the 'Promise' inform the JS engine to invoke(execute/run) the Promise(executor)function. The 'executor' function first does it's job,then based on the result, calls one of the callback function's; either resolve and reject.
  4. When the inner function, 'setTimeout' is invoked, the JS engine creates a whole new 'execution context' for the function. And, the function gets placed on the call-stack on top of the Promise() function, and others if still pending. (Sorry, I don't have room for explaining the heap, stack, WebAPI'S, event loop, or the callback queue. All important topics on their own).
  5. Once a promise is fulfilled or rejected the appropriate handler, either the 'onFulFilled' or the 'onRejected' will run 'asynchronously'.
  6. The 'then()' method basically says, 'return my promise'.
  7. The 'then()' method is also used for method chaining. (To be covered in my next post)

We can also use the 'catch()' method of the parent object: [Promise].
Instance objects have access to the properties and methods on the parent object - Promise, through the prototype-chain.

Thank you for taking time out to read my post. I am a self-taught web developer. So, when you find my mistakes...please inform so that I can correct and better yet...continue to learn from them.

TC

Top comments (0)