Creating a (ES6) promise without starting to resolve it

JavascriptPromiseEs6 Promise

Javascript Problem Overview


Using ES6 promises, how do I create a promise without defining the logic for resolving it? Here's a basic example (some TypeScript):

var promises = {};
function waitFor(key: string): Promise<any> {
  if (key in promises) {
    return promises[key];
  }
  var promise = new Promise(resolve => {
    // But I don't want to try resolving anything here :(
  });

  promises[key] = promise;
  return promise;
}

function resolveWith(key: string, value: any): void {
  promises[key].resolve(value); // Not valid :(
}

It's easily done with other promise libraries. JQuery's for example:

var deferreds = {};
function waitFor(key: string): Promise<any> {
  if (key in promises) {
    return deferreds[key].promise();
  }
  var def = $.Deferred();    
  deferreds[key] = def;
  return def.promise();
}

function resolveWith(key: string, value: any): void {
  deferreds[key].resolve(value);
}

The only way I can see to do this would be to store the resolve function away somewhere within the promise's executor but that seems messy, and I'm not sure it's defined when exactly this function is run - is it always run immediately on construction?

Thanks.

Javascript Solutions


Solution 1 - Javascript

Good question!

The resolver passed to the promise constructor intentionally runs synchronous in order to support this use case:

var deferreds = [];
var p = new Promise(function(resolve, reject){
    deferreds.push({resolve: resolve, reject: reject});
});

Then, at some later point in time:

 deferreds[0].resolve("Hello"); // resolve the promise with "Hello"

The reason the promise constructor is given is that:

  • Typically (but not always) resolution logic is bound to the creation.
  • The promise constructor is throw safe and converts exceptions to rejections.

Sometimes it doesn't fit and for that it the resolver runs synchronously. Here is related reading on the topic.

Solution 2 - Javascript

I want to add my 2 cents here. Considering exactly the question "Creating a es6 Promise without starting resolve it" I solved it creating a wrapper function and calling the wrapper function instead. Code:

Let's say we have a function f which returns a Promise

/** @return Promise<any> */
function f(args) {
   return new Promise(....)
}

// calling f()
f('hello', 42).then((response) => { ... })

Now, I want to prepare a call to f('hello', 42) without actually solving it:

const task = () => f('hello', 42) // not calling it actually

// later
task().then((response) => { ... })

Hope this will help someone :)


Referencing Promise.all() as asked in the comments (and answered by @Joe Frambach), if I want to prepare a call to f1('super') & f2('rainbow'), 2 functions that return promises

const f1 = args => new Promise( ... )
const f2 = args => new Promise( ... )

const tasks = [
  () => f1('super'),
  () => f2('rainbow')
]

// later
Promise.all(tasks.map(t => t()))
  .then(resolvedValues => { ... })

Solution 3 - Javascript

How about a more comprehensive approach?

You could write a Constructor that returns a new Promise decorated with .resolve() and .reject() methods.

You would probably choose to name the constructor Deferred - a term with a lot of precedence in [the history of] javascript promises.

function Deferred(fn) {
	fn = fn || function(){};

	var resolve_, reject_;

	var promise = new Promise(function(resolve, reject) {
		resolve_ = resolve;
		reject_ = reject;
		fn(resolve, reject);
	});
	
	promise.resolve = function(val) {
		(val === undefined) ? resolve_() : resolve_(val);
		return promise;//for chainability
	}
	promise.reject = function(reason) {
		(reason === undefined) ? reject_() : reject_(reason);
		return promise;//for chainability
	}
	promise.promise = function() {
		return promise.then(); //to derive an undecorated promise (expensive but simple).
	}
	
	return promise;
}

By returning a decorated promsie rather than a plain object, all the promise's natural methods/properties remain available in addition to the decorations.

Also, by handling fn, the revealer pattern remains availble, should you need/choose to use it on a Deferred.

DEMO

Now, with the Deferred() utility in place, your code is virtually identical to the jQuery example.

var deferreds = {};
function waitFor(key: string): Promise<any> {
  if (key in promises) {
	return deferreds[key].promise();
  }
  var def = Deferred();    
  deferreds[key] = def;
  return def.promise();
}

Solution 4 - Javascript

Things are slowly getting better in JavaScript land, but this is one case where things are still unnecessarily complicated. Here's a simple helper to expose the resolve and reject functions:

Promise.unwrapped = () => {
  let resolve, reject, promise = new Promise((_resolve, _reject) => {
    resolve = _resolve, reject = _reject
  })
  promise.resolve = resolve, promise.reject = reject
  return promise
}

// a contrived example

let p = Promise.unwrapped()
p.then(v => alert(v))
p.resolve('test')

Apparently there used to be a Promise.defer helper, but even that insisted on the deferred object being separate from the promise itself...

Solution 5 - Javascript

What makes this kind of issues look complicated is the procedural nature of Javascript I guess. To resolve this, I created a simple class. Here how it looks:

class PendingPromise {

    constructor(args) {
        this.args = args;
    }

    execute() {
        return new Promise(this.args);
    }
}

This promise will only be executed when you call execute(). For example:

function log() {
    return new PendingPromise((res, rej) => {
        console.log("Hello, World!");
    });
}

log().execute();

Solution 6 - Javascript

CPomise allows you to resolve your promises outside, but this is an antipattern since it breaks Promise incapsulation model. (Live demo)

import CPromise from "c-promise2";

const promise = new CPromise(() => {});

promise.then((value) => console.log(`Done: ${value}`)); //123

setTimeout(() => promise.resolve(123));

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBarguastView Question on Stackoverflow
Solution 1 - JavascriptBenjamin GruenbaumView Answer on Stackoverflow
Solution 2 - JavascriptManu ArteroView Answer on Stackoverflow
Solution 3 - JavascriptRoamer-1888View Answer on Stackoverflow
Solution 4 - JavascriptseanlinsleyView Answer on Stackoverflow
Solution 5 - JavascriptSalih KavafView Answer on Stackoverflow
Solution 6 - JavascriptDmitriy MozgovoyView Answer on Stackoverflow