2

I'm trying to write a promise polyfill to get a better understanding of promise. I've searched the internet and found a code which I'm able to understand to some extent.

function CustomPromise(executor) {
var state=PENDING;
var value = null;
var handlers=[];
var catchers = [];

function resolve(result) {
    if(state!==PENDING) return;

    state=FULFILLED;
    value = result;

    handlers.forEach((h)=>h(value));    //this line
}

function reject(error) {
    if(state!==PENDING)return;

    state=REJECTED;
    value=error;
    catchers.forEach(c=>c(value));  // this line
}

this.then = function(successCallback) {
    if(state === FULFILLED) {
        successCallback(value);
    }else {
        handlers.push(successCallback);
    }
    return this;
}

this.catch = function(failureCallback) {
    if(state===REJECTED){
        failureCallback(value)
    } else {
        catchers.push(value);
    }
}
executor(resolve,reject);
}

Even in this I'm unable to understand the use of handlers and catchers. It was said that they are for situation when promise is not fulfilled or rejected. Explaining these two lines will also help. Now, the actual issue with above implementation is it doesn't work for when used like let p1 = Promise.resolve("Hello World");. I have tried converting it to class based but I'm unable to do that. My attempt:

class CustomPromise {
constructor(callback){
    this.state = PENDING;
    this.executor = callback;
    this.value = null;
    this.handlers = [];
    this.catchers = [];
    this.then = function(successCallback) {
        if(this.state === FULFILLED) {
            successCallback(this.value);
        }else {
            this.handlers.push(successCallback);
        }
        return this;
    };

    this.catch = function(failureCallback) {
        if(this.state===REJECTED){
            failureCallback(this.value)
        } else {
            this.catchers.push(this.value);
        }
    };
}

static resolve(result) {
    if(this.state!==PENDING) return;

    this.state=FULFILLED;
    this.value = result;

    this.handlers.forEach((h)=>h(this.value));
    // return new CustomPromise( function ( fulfil ) {
    //     fulfil( value );
    // });
}

static reject(error) {
    if(this.state!==PENDING)return;

    this.state=REJECTED;
    this.value=error;
    this.catchers.forEach(c=>c(this.value));
}

// executor(resolve,reject);
}

Can someone correct the functional approach so that it works for CustomPromise.resolve() scenario or correction in my class based approach will also be appreciated. EDIT: Tried CustomPromise.prototype.resolve = function(error) {...} still getting same error CustomPromise.resolve is not a function EDIT2 : In class based approach I'm unable to implement executor callback. I just want either one of the approach to work for case like Promise.resolve()

era s'q
  • 537
  • 1
  • 7
  • 27
  • I agree this question could be closed, but I don't understand why this was closed with this dupe reference... @VLAZ? The asker already used `static` -- which is the right way to make their case work... The question is more about the *implementation* of that static method. – trincot Jun 30 '22 at 16:53
  • @trincot the "functional approach" mentioned (constructor function) doesn't work because the `resolve` and `reject` are not assigned as static methods. They are just functions declared inside but not visible outside. – VLAZ Jun 30 '22 at 16:55
  • Yes, but they already went to the `static` method further down the question... So how will the dupe reference help them?? – trincot Jun 30 '22 at 16:56
  • @eras'q, your attempt in the static method that is put in comments, was the better one. Why did you put this in comments? – trincot Jun 30 '22 at 16:56
  • @trincot static approach was throwing same error. – era s'q Jun 30 '22 at 16:59
  • @trincot In the class based approach I was unable to implement the executor callback, catchers, handlers. – era s'q Jun 30 '22 at 17:00
  • @trincot "Can someone correct the functional approach so that it works for CustomPromise.resolve() scenario" is what lead me to believe that's what the question is about. Upon re-reading it's really several questions related to the same issue but from completely different angles. And with several different problems. None of them especially explicit: "*I have tried converting it to class based but I'm unable to do that.*" isn't really specific to what goes wrong. The question should really just focus on one thing at a time and clarify what the problem is. Reopening, as it's not a dupe ATM. – VLAZ Jun 30 '22 at 17:00

1 Answers1

3

To extend the first (working) version of CustomPromise, add the code you seem to have been trying with in commented-out code in the (non-working) class-version you had. But it had two little problems. Still, the idea to use new CustomPromise is the right one:

CustomPromise.resolve = value => new CustomPromise(fulfil => fulfil(value));
CustomPromise.reject = error => new CustomPromise((_, reject) => reject(error));

So if you add that below your CustomPromise function definition, it'll work:

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

function CustomPromise(executor) {
    var state=PENDING;
    var value = null;
    var handlers=[];
    var catchers = [];

    function resolve(result) {
        if(state!==PENDING) return;

        state=FULFILLED;
        value = result;

        handlers.forEach((h)=>h(value));
    }

    function reject(error) {
        if(state!==PENDING)return;

        state=REJECTED;
        value=error;
        catchers.forEach(c=>c(value));
    }

    this.then = function(successCallback) {
        if(state === FULFILLED) {
            successCallback(value);
        }else {
            handlers.push(successCallback);
        }
        return this;
    }

    this.catch = function(failureCallback) {
        if(state===REJECTED){
            failureCallback(value)
        } else {
            catchers.push(value);
        }
    }
    executor(resolve,reject);
}

// Added:
CustomPromise.resolve = value => new CustomPromise(fulfil => fulfil(value));
CustomPromise.reject = error => new CustomPromise((_, reject) => reject(error));

// Demo
let p = CustomPromise.resolve(42);
let q = CustomPromise.reject("custom error");
p.then(value => console.log("p", value));
q.catch(error => console.log("q", error));

Disclaimer: this polyfill is not compliant with the Promises/A+ specification let be it would be compliant with the ECMAScript specification for Promise.

Some of the problems it has:

  • It allows the then callback to be executed synchronously. The then callback should never be executed synchronously, but always be called via a queued job (i.e. asynchronously).
  • The catch method wrongly returns undefined. It should return a promise.
  • The then method wrongly returns the same promise as it is called on. It should return a different promise, that resolves with the value that will be returned by the callback.
  • The then method ignores a second argument. It should take a second function which should serve as a rejection callback (like catch).
  • When the promise is resolved with a thenable, the thenable is wrongly used as fulfillment value. This is not how it is supposed to work. A very important aspect of promises, is that promises chain, i.e. when a promise resolves to another promise (or thenable), it gets "locked in" to that second promise, so that it will follow the way that second promise resolves.

There are many correct implementations to be found. I posted my own Promises/A+ compliant implementation in this answer. It does not have the resolve and reject static methods, but it would require the same additional code as given above.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • Thank you for the answer and the reference. I have one doubt. `new CustomPromise((_, reject)` why did you add underscore as first parameter ? because while invoking we only have one argument. `let q = CustomPromise.reject("custom error");` – era s'q Jun 30 '22 at 17:49
  • 1
    The executor that we pass to `CustomerPromise` must be a function that accepts two callback functions. In the case of `.reject` we want to use the *second* argument that the executor will receive. Be aware that this executor is a completely different function, not to be confused with the `CustomPromise.reject` function, even though I have named that argument `reject` -- it is not this `CustomPromise.reject`. – trincot Jun 30 '22 at 20:49