javascript custom promise

JavaScript Custom Promise | Promise Polyfill

👉Let’s First Write JavaScript InBuilt Promise

// This is promise callback function
let promiseFn = (resolve, reject) => {
    setTimeout(() => {
        resolve('I am JavaScript Inbuilt Promise');
    }, 2000);
}

// Creating Promise using Promise Constructor (It takes Promise callback function as a parameter)
let p = new Promise(promiseFn);

// Promise then method (It takes success callback as a parameter)
p.then((data) => { console.log(data) });

// Promise catch method (It takes error callback as a parameter)
p.catch(console.log);

💡Understand the Promise approach

– Promise function takes one parameter and that is an executable function.
– When we call this Promise with the new keyword, it returns a promise object.
– Executable function is like a higher order function that takes two function arguments named resolve and reject.
– Promise has two public methods here, then and catch.
Then and Catch method also takes callbacks which executes once the promise is resolved or rejected.

Promise Pollyfill | Custom Promise

In this Pollyfill, we are going to implement –

  1. A Promise Class
  2. Then Method
  3. Catch Method

Out of Scope Or which we will not implement here –

  1. Promise Chaining
  2. Finally method
  3. Other Promise APIs like Promise.all, any, race, etc.

Code

// These are predefined states of a promise.
const PENDING = 'PENDING';
const RESOLVED = 'RESOLVED';
const REJECTED = 'REJECTED';

function MyPromise(promiseFn) {
    let state = PENDING;
    let successData;
    let error;
    let handlerCb = [];
    const resolve = function (response) {
        successData = response;
        state = RESOLVED;
        console.log(handlerCb);
        handlerCb.forEach(cb => {
            queueMicrotask(() => cb(response));
        });

    }

    const reject = function (err) {
        state = REJECTED;
        error = err;
        if (!handlerCb) {
            throw new Error(err);
        }
        handlerCb.forEach(cb => {
            queueMicrotask(() => cb(error));
            throw new Error(err);
        });
    }
    this.then = function (cb) {
        if (state === RESOLVED) {
            queueMicrotask(() => cb(successData));
        } else {
            handlerCb.push(cb);
        }

    }

    this.catch = function (cb) {
        if (state === REJECTED) {
            queueMicrotask(() => cb(error));
        } else {
            handlerCb.push(cb);
        }
    }

    promiseFn(resolve, reject);
}

💻 Run the above program

let promiseFn1 = (resolve, reject) => {
    setTimeout(() => {
        resolve('hello I am promise polyfill');
    }, 2000);
}

const p1 = new MyPromise(promiseFn1);

p1.then((data) => { console.log('then 1', data) });

// we can add multiple then on same object thats why in polyfill we have handlerCb as an array.
p1.then((data) => { console.log('then 2', data) });


👉 Let’s Understand the above Pollyfill step by step –

  • The default state is Pending.
  • Created a function named MyPromise which will take the promise function as argument.
  • This Promise executable function requires a resolve and reject method as a parameter to execute.

  • First Implement then method –
    • We call then method of the promise object with a success callback as a parameter.
    • Example – p.then(() => {})
    • Create a then method with a callback argument.
    • As we know then’s callback does not execute immediately. This runs after resolve only.
    • So what can we do? we can store this callback fn in an array named handlerCb.
    • One thing we can also do is that in case Promise is already resolved then we can execute this here only.
    • To check Promise resolved we can check its state. (state === RESOLVED)

  • Implement Resolve method
    • It calls inside the promise function, Once our task is completed then we can call the resolve method with a success value.
    • Create a resolve method with a parameter (success Value or response) inside our class.
    • After that execute handlerCb function that we stored from then method.

  • Implement Catch method
    • It also takes error callback as a parameter.
    • Follow the same approach as then method.
    • If the state is rejected then execute the error callback immediately.
    • Else store this callback in the same array named handlerCb.

  • Implement Reject method
    • Mark state a rejected.
    • Call error callback which we stored in handlerCb variable.
    • As we know Promise reject method also throws an error so we can throw an error with code – throw new Error(err).
    • One extra check we can add is If someone does not implement the catch method then also throw an error.
  • And last, call that promise callback function with the implemented resolve and reject method.


💡Other Notes | Keep In Mind things –

– We are executing callbacks inside the public API queueMicrotask because Promise callbacks always run in async.
– To correctly emulate the behavior we have to call it inside a microtask queue so that it is called async &
should have higher priority then normal async cbs.
– Loop over handlers callbacks and execute one one one.
– Promise then and catch method are only public methods so we add these in this object.
– resolve and reject are private methods.

JavaScript Custom Promise

const p2 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve('HI I will run after 2 seconds')
    }, 2000);
})

const p3 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        reject('HI I will rejected after 2 seconds')
    }, 2000);
})

p2.then(console.log)

p3.then(console.log)
p3.catch(console.log)

const p4 = new MyPromise((resolve, reject) => {
    resolve('HI I will resolved immediately seconds')
})
p4.then(console.log);

 CLASS BASED STRUCTURE Of Promise Polyfill

Note Here: Just one thing to keep in mind is that we need to resolve and reject methods before promiseFn execution, so we have to create those inside the constructor function, not outside.

code

// These are predefined states of a promise.
const PENDING = 'PENDING';
const RESOLVED = 'RESOLVED';
const REJECTED = 'REJECTED';
class MyPromise1 {
    state = PENDING;
    successData;
    error;
    handlerCb;
    constructor(promiseFn) {
        // resolve and reject must be created here only. Cannot be created outside because we need these to call promiseFn.
        const resolve = (response) => {
            this.successData = response;
            this.state = RESOLVED;
            queueMicrotask(() => this.handlerCb && this.handlerCb(this.successData));
        }
        const reject = (err) => {
            this.state = REJECTED;
            this.error = err;
            if (!this.handlerCb) {
                throw new Error(err);
            }
            queueMicrotask(() => {
                this.handlerCb && this.handlerCb(this.error);
                throw new Error(this.error);
            });
        }
        promiseFn(resolve, reject);
    }
    then(cb) {
        if (this.state === RESOLVED) {
            queueMicrotask(() => cb(this.successData));
        } else {
            this.handlerCb = cb;
        }
    }

    catch(cb) {
        if (this.state === REJECTED) {
            queueMicrotask(() => cb(this.error));
        } else {
            this.handlerCb = cb;
        }
    }
}

💻 Run the above program

const c = new MyPromise1((resolve, reject) => {
    resolve('HI I will resolved immediately seconds in Class MyPromise1')
});
c.then(console.log);

const c1 = new MyPromise1((resolve, reject) => {
    setTimeout(() => {
        resolve('HI I will resolved after 3 seconds in Class MyPromise1')
    }, 3000);
});
c1.then(console.log);

const c2 = new MyPromise1((resolve, reject) => {
    setTimeout(() => {
        reject('HI I will rejected after 4 seconds in Class MyPromise1')
    }, 4000);
});
c2.catch(console.log);

JavaScript Custom Promise