Creating a promise without the new keyword

Viewed 3145

After watching fun fun function, I decided not to use the new keyword.

But here is a foundational example of how to use promises:

var promise = new Promise(function(resolve, reject) {
  // do a thing, possibly async, then…

  if (/* everything turned out fine */) {
    resolve("Stuff worked!");
  }
  else {
    reject(Error("It broke"));
  }
});

Q: How do I create a promise without the use of the new keyword?

4 Answers

As a pure academic question, you could work around new by creating a thenable (for which you don't need new), and then you can convert that thenable to a full-blown promise with Promise.resolve:

function createPromise(executor) {
    let callback;
    function resolve(resolution, arg) {
        Promise.resolve().then(function () {
            callback[resolution](arg);
        });
    }
    try {
        executor(resolve.bind(null, "fulfill"), resolve.bind(null, "reject"));
    } catch(e) {
        resolve("reject", e);
    }
    return Promise.resolve({ 
        then: function (fulfill, reject) {
            callback = { fulfill, reject };
        }
    });
}

// Demo
var prom1 = createPromise(function (resolve, reject) {
    setTimeout(function () { resolve("Stuff worked!") }, 1000);
});
var prom2 = createPromise(function (resolve, reject) {
    setTimeout(function () { reject("It broke") }, 1000);
});

prom1.then(function (v) { console.log(v); });
prom2.catch(function (err) { console.log(err) });
console.log('waiting for promises to resolve');

Some have reasons to avoid the use of new, but if that has to lead to the above code, then clearly some exceptions should be allowed. Unless someone has a better idea to create promises without the use of new, one has to implement some Promise characteristics again (such as the try ... catch and the asynchronous call of the then callback) only to work around new. This seems a bad idea.

Conclusion: just use new for creating new promises.

Use async/await

const getPromise = async () => {
    // do a thing, possibly async, then…

    if (/* everything turned out fine */) {
        return 'Stuff worked!'
    } else {
        throw 'It broke'
    }
});
const promise = getPromise()

But please realize that you can't just call Error, calling constructor without new keyword throws garbage to global object in NodeJS (or to window object in browser JS). You have to use new Error. I hope I didn't create a whole new problem for you.

Also, note that async/await is just a syntactic sugar, the code will run with new anyway. Also, I suggest not to take advice literally. Granted, new is evil, but sometimes necessary in order not to over-complicate things.

MDN's Promise.resolve() and Promise.reject() man pages show how those functions return thenables.

The following code snippet goes a step further to show how they can be used to make myFunc wrap _myFunc to return a thenable.

'strict';

function _myFunc(tf){
  if (tf)
    return; 
  throw new Error('flubbed it');
}
function myFunc(tf){
  try { return Promise.resolve(_myFunc(tf));}
  catch(e) { return Promise.reject(e);}
}

function main() {
  try {
    _myFunc(false);
    console.log('SUCCESS');
  } catch (e) {
    console.log(`FAILED: ${e.message}`);
  }

  try {
    _myFunc(true);
    console.log('SUCCESS');
  } catch (e) {
    console.log(`FAILED: ${e.message}`);
  }

  myFunc(false)
    .then(()=>{ console.log('success'); })
    .catch((e)=>{ console.log(`failed: ${e.message}`); });

  myFunc(true)
    .then(()=>{ console.log('success'); })
    .catch((e)=>{ console.log(`failed: ${e.message}`); });

}

main();

However, because there are no promises, there is no await either, and we are back to the pyramid of doom when it comes to synchronizing. As you can see in the output

FAILED: flubbed it
SUCCESS
success
failed: flubbed it

the order of last two results are out of order. That is because the JS interpreter sees the use of a thenable as an instruction to run the command asynchronously.

In conclusion, the great thing about Promises is the ability to await and use Promise.all or Promise.race etc, which require Promises with state, for which new Promise(...) is obviously required.

You can use a wrapper function:

const Guarantee = function () {
  var _resolve = null
  var _reject = null

  var promise = new Promise(function (resolve, reject) {
    _resolve = resolve
    _reject = reject
  })

  promise.resolve = function () {
    _resolve.apply(promise, arguments)
  }

  promise.reject = function () {
    _resolve.apply(promise, arguments)
  }

  return promise
}

function doSomething () {
  const promise = Guarantee()

  setTimeout(function () {
    promise.resolve('123')
  }, 1000)

  return promise
}

doSomething()
  .then(function (data) {
    console.log(data)
  })

Enjoy!

Related