Basic JavaScript Promise Implementation Attempt

Simple Promise and Then implementation

Here's the shortened code for creating a promise class,

class MyPromise {  constructor(executor) {    this.callbacks = [];
const resolve = res => { for (const { callback } of this.callbacks) { callback(res); } };
executor(resolve); }
then(callback) { return new MyPromise((resolve) => { const done = res => { resolve(callback(res)); }; this.callbacks.push({ callback: done }); }); }}

promise = new MyPromise((resolve) => { setTimeout(() => resolve(2), 1000);});
promise.then(result => { console.log(result); return 2 * result;}).then(result => console.log(result));

How to implement a simpler Promise in JavaScript?

With the comments that you guys provide me, I was able to improve the code and correct the errors mentioned, as shown below. Now, I would like you to give me suggestions on how to proceed and improve the code. Thanks. (The code can also be found on github).

const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2;

function _Promise(executor) {

let state = PENDING;
let callOnFulfilled = [];
let callOnRejected = undefined;;

function resolve(...args) {
if (!state) {
state = FULFILLED;
}

resolveCallbacks(...args);
};
function reject(error) {
state = REJECTED;
if (callOnRejected && (callOnRejected instanceof Function)) {
callOnRejected(error);
callOnRejected = undefined;
callOnFulfilled = [];
} else {
throw `Unhandled Promise Rejection\n\tError: ${error}`;
}
};
function resolveCallbacks(...value) {
if (state !== REJECTED) {
let callback = undefined;
do {
callback = callOnFulfilled.shift();
if (callback && (callback instanceof Function)) {
const result = callback(...value);
if (result instanceof _Promise) {
result.then(resolveCallbacks, reject);
return;
} else {
value = [result];
}
}
} while (callback);
}
};

if (executor && (executor instanceof Function)) {
executor(resolve, reject);
}

this.then = function (onFulfilled, onRejected) {
if (onFulfilled) {
callOnFulfilled.push(onFulfilled);
if (state === FULFILLED) {
resolveCallbacks();
}
}
if (onRejected && !callOnRejected) {
callOnRejected = onRejected;
}
return this;
};
this.catch = function (onRejected) {
return this.then(undefined, onRejected);
};
}

function sum(...args) {
let total = 0;
return new _Promise(function (resolve, reject) {
setTimeout(function () {
for (const arg of args) {
if (typeof arg !== 'number') {
reject(`Invalid argument: ${arg}`);
}
total += arg;
}
resolve(total);
}, 500);
});
}

console.time('codeExecution');
sum(1, 3, 5).then(function (a) {
console.log(a);
return sum(2, 4).then(function (b) {
console.log(b);
return sum(a, b).then(function (result) {
console.log(result);
return 25;
});
}).then(function (value) {
console.log(value);
console.timeEnd('codeExecution');
});
}).catch(function (error) {
console.log(error);
});

How JavaScript promises work behind the scenes

The following is a simplified implementation of the built-in Promise class. catch and finally have not been implemented.

The function supplied to the Promise constructor is called the executor function, and is invoked immediately and synchronously.

Every promise has a method .then, enabling the chaining of promises.

Functions supplied to .then are always invoked asynchronously on a microtask (note use of queueMicrotask below).

Every time .then is called, a new promise is created and returned.

.then can be called more than once on the same promise, creating a multicast of the result of the promise, and a branching of the promise chain.

A promise can be in one of three states: pending, fulfilled, or rejected. State transitions are unidirectional: you cannot move from fulfilled or rejected, back to pending.

If a promise is resolved with another promise, then the two promise chains are joined and the outer promise takes on the status of the inner promise (which could be pending), until the inner promise resolves.

function Promise(executor) {  if (!executor) throw "Promise executor undefined"  let status = "pending", value, thenQ = []
const then = onFulfilled => { let resolver // This ensures control does not move to later promises // until prior promises have been resolved. const nextPromise = new Promise(resolve => (resolver = resolve)) // More than one "then" can be registered with each promise. thenQ.push((...args) => resolver(onFulfilled(...args))) return nextPromise }
// We check if the result is a "thenable"; if so, we treat // it as an inner promise, otherwise we simply fulfil with // the result. const resolve = result => result?.then ? result.then(fulfil) : fulfil(result)
// When a promise has been fulfilled, its "thens" can be run. const fulfil = result => (status = "fulfilled", value = result, executeThens(value))
// "Thens" are run asynchronously, on a microtask. const executeThens = value => queueMicrotask(() => thenQ.forEach(el => el(value)))
// The executor is run synchronously. executor(resolve)
return { then, get status() { return status }, get value() { return value } }}
// Chainingnew Promise(resolve => { console.log('Waiting for step 1...') setTimeout(() => resolve("One, two..."), 1500)}).then(result => new Promise(resolve => { console.log('Waiting for step 2...') setTimeout(() => resolve(`${result}three, four`), 1500)})).then(result => console.log(`Chaining result: ${result}.`))
// Branchingconst p = new Promise(resolve => { console.log('Waiting for step a...') setTimeout(() => resolve("Alpha, Bravo..."), 1500)})
p.then(result => new Promise(resolve => { console.log('Waiting for step b1...') setTimeout(() => resolve(`${result}Charlie, Delta`), 1500)})).then(console.log)
p.then(result => { console.log('Waiting for step b2...') return `${result}Echo, Foxtrot`}).then(console.log)

Understanding setTimeout and its implementation in JavaScript Promises

This is not a correct Promise implementation. It clearly has no capabilities for rejections, but also for the implemented fulfilment feature it does not comply with the Promises/A+ specification. Just to give two examples:

  1. It does not comply with rule 2.1.2.2

    When fulfilled, a promise must have a value, which must not change.

    ....nor with rule 2.2.2.3:

    If onFulfilled is a function it must not be called more than once.

    In your implementation, if you would add a second call to resolve:

    new MyPromise(function(resolve, reject) {
    resolve('new message'); resolve('new message2');
    }).then((function(message) {
    console.log(message);
    // ... etc
    })

    ...then both calls to resolve would fire the then callback, and show that the promised value was modified after the first time it was set. This is in complete violation of the principle of promises: promises can only resolve once.

  2. It does not comply with rule 2.2.6:

    then may be called multiple times on the same promise.

    • If/when promise is fulfilled, all respective onFulfilled callbacks must execute in the order of their originating calls to then.

    This does not happen if we use the following code:

    let p = new MyPromise(resolve => resolve("value"));
    p.then(console.log); // This callback is called
    p.then(console.log); // This callback is not called -> violation!

These are basic shortcomings, and this is just the tip of the iceberg.

If you are interested in how it could be implemented in compliance with Promise/A+, then have a look at one I did a few years ago with a step-by-step explanation.

As to your question

how does the program even get to the setTimeout?

When your main code executes this:

new MyPromise(function(resolve, reject) {
resolve('new message');
})

...then the parameter variable configFunction is initialised with that callback function. The MyPromise constructor calls it, passing it the following callback as first argument:

  function(message){
setTimeout(function(){
if(nextSuccessCallBack) {
var result = nextSuccessCallBack(message);
if(result && result.then) {
result.then(nextResolve);
} else {
nextResolve && nextResolve(result);
}
}
})
}

So that means that resolve in your main code's callback function is initialised with the above callback function. Then your main code's callback function calls resolve with a string argument. Since resolve is the above callback function, we can see it gets executed with message initialised to "new message". This function executes setTimeout.

Trying to implement a SIMPLE promise in Reactjs

You are using React in a wrong way. A Promise is designed to return result at a later point of time. By the time your promise has been resolved or rejected, your render would have finished execution and it wont update when the promise completes.

render method should only depend on props and/or state to render the desired output. Any change to prop or state would re-render your component.

  • First, identify where your Promise should go in the life cycle of the component(here)
  • In your case i would do the following

    • Initialize an state inside your constructor(ES6) or via getInitialState

      constructor(props) {
      super(props);
      this.state = {
      name: '',
      };
      }
    • Then on componentWillMount or componentDidMount which ever suits you, call the promise there

      componentWillMount() {
      var promise = new Promise( (resolve, reject) => {

      let name = 'Paul'

      if (name === 'Paul') {
      resolve("Promise resolved successfully");
      }
      else {
      reject(Error("Promise rejected"));
      }
      });

      let obj = {newName: ''};

      promise.then( result => {
      this.setState({name: result});
      }, function(error) {
      this.setState({name: error});
      });
      }
    • Then in render method write something similar to this.

      render() {
      return (
      <div className="App">
      <h1>Hello World</h1>
      <h2>{this.state.name}</h2>
      </div>
      );
      }


Related Topics



Leave a reply



Submit