113

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.

Barguast
  • 5,926
  • 9
  • 43
  • 73
  • 1
    WTH would you do something like that? A promise without resolve-logic is a forever-pending promise. – Bergi Jun 26 '15 at 09:35
  • Your second part of the question is a duplicate of [Is JavaScript Promise Callback executed Asynchronosuly](http://stackoverflow.com/q/29963129/1048572) – Bergi Jun 26 '15 at 09:38
  • 2
    @Bergi - Imagine something like an asynchronous dependency injection system. You have one part where you're registering injection items, and others where you're requesting them. If I request an item that hasn't been registered yet, then I'll want to return a promise that'll resolve once it has. – Barguast Jul 20 '15 at 13:00
  • 1
    See also the possible duplicate [Promises for promises that are yet to be created without using the deferred \[anti\]pattern](http://stackoverflow.com/q/37426037/1048572) – Bergi Jun 06 '16 at 17:59

6 Answers6

125

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.

Community
  • 1
  • 1
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • I was thinking along the lines of `Promise.resolve` and storing the bound `then`. But, this looks clean and I am not even sure if the bound `then` will work. – thefourtheye Jun 26 '15 at 09:27
  • Wow. Thanks for the incredibly fast and thorough answer! Do you happen to have a source stating that executor (the function with the resolve and reject arguments) always runs synchronously at the time of construction? – Barguast Jun 26 '15 at 09:30
  • @thefourtheye bound `then`s would not work if you don't implement your own `then` function. – Benjamin Gruenbaum Jun 26 '15 at 09:31
  • 2
    @Barguast sure, for ES6 in particular - http://www.ecma-international.org/ecma-262/6.0/index.html#sec-newpromisecapability is how promises are constructed, which calls http://www.ecma-international.org/ecma-262/6.0/index.html#sec-construct is called synchronously. It is in turn called synchronously from http://www.ecma-international.org/ecma-262/6.0/index.html#sec-promise-executor - I think the old https://github.com/promises-aplus/constructor-spec is a better source. – Benjamin Gruenbaum Jun 26 '15 at 09:34
  • 2
    @BenjaminGruenbaum: Just point to [Is JavaScript Promise Callback executed Asynchronosuly](http://stackoverflow.com/q/29963129/1048572) :-) – Bergi Jun 26 '15 at 09:39
  • @Bergi oh nifty! I completely forgot about that - I'll add a link to it thanks. – Benjamin Gruenbaum Jun 26 '15 at 09:40
  • Perfect. Thanks very much. – Barguast Jun 26 '15 at 09:44
  • Is assigning the `new Promise` to a variable (`var p`) necessary? – papiro Aug 07 '16 at 10:02
  • @papiro no, it is only there as a didactic step to show that you get the promise returned and can use it as a regular promise. It is not necessary though I can't really think about what you can accomplish without using the actual promise in some way. – Benjamin Gruenbaum Aug 07 '16 at 13:22
  • If you have multiple promises, it sounds like this method runs them all together because by calling `var p = new Promise`, the promise starts its execution. Looks like you are synchronizing their resolution (end of execution) not the start of execution. – Ari Jun 14 '17 at 07:56
  • It should be the part of Promise API with the special state. – Eugene Hoza Oct 02 '18 at 07:38
  • 1
    @EugeneHoza it used to be (a long time ago) but it was changed to the revealing constructor pattern (which was considered safer). I used to love it - but in retrospect I'm not sure it's actually safer - it's just a tradeoff. You can read about it here: https://blog.domenic.me/the-revealing-constructor-pattern/ - in addition if you feel passionately about it (after you've read the relevant background of course) - you're very welcome to make a suggestion to TC39 to introduce another API. – Benjamin Gruenbaum Oct 02 '18 at 08:22
73

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 => { ... })
Manu Artero
  • 9,238
  • 6
  • 58
  • 73
  • How can this be used with `Promise.all()` ? – Frondor Mar 01 '18 at 03:51
  • tasks = [() => f(1), () => f(2)]; Promise.all(tasks.map(t => t())).then(... – 000 Mar 28 '18 at 20:57
  • @Frondor I also came here wanting to use promises in my `redux saga` unit testion. by creating a curried function. `function*f()` which is way of still during `expectSaga()` unit testing. f() -> g() -> Promise – CDM social medias in bio Apr 12 '21 at 22:50
  • In combination with await this is exactly what I needed to setup tasks and then wait for them all to finish. So in my case the last line becomes: await Promise.all(tasks.map(t => t())); – Dirk Vermeer Aug 05 '21 at 07:59
4

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...

seanlinsley
  • 3,165
  • 2
  • 25
  • 26
3

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();
}
Roamer-1888
  • 19,138
  • 5
  • 33
  • 44
  • 1
    I don't know, this seems to re-introduce all the things that we wanted to avoid when switching from deferreds to promises and the constructor approach. Also, your implementation is totally overcomplicated, and contains a few mistakes: a) if you give `Deferred` a callback parameter, it should be called back with the deferred b) testing the `value`/`reason` for `undefined` is absolutely unnecessary c) `.resolve` and `.reject` never need to be chained d) your `.promise` method doesn't return an undecorated promise – Bergi Jun 27 '15 at 10:01
  • I've always been uneasy about designing out Deferreds. I'm sure they are rare but there must be usage cases when a Deferred is necessary - ie when the means for settlement cannot be defined at the time a Promise is created. I don't understand (a), I'm open to persuasion on (b) and you're certainly right about (c). – Roamer-1888 Jun 27 '15 at 11:07
  • I mean that you should do `fn(promise)` (or rather, `fn(deferred)` actually) instead of `fn(resolve, reject)`. Yes, I can certainly see a need for "resolver objects" that can be stored somewhere and expose `.fulfill` and `.reject` methods, but I think those should not implement the promise interface. – Bergi Jun 27 '15 at 12:07
  • @Bergi, I understand that jQuery, for example, reveals the Deferred itself as a callback arg. Maybe I'm missing something but I can't see why a Deferred implementation *shouldn't* mimic the de facto practice for `new Promise(fn)` of revealing just `resolve` and `reject`. – Roamer-1888 Jun 27 '15 at 13:39
  • Also, whereas the `when.js` documentation discourages use of its `when.defer`, it does acknowledge that `in certain (rare) scenarios it can be convenient to have access to both the promise and it's associated resolving functions`. If that statement is to be believed, then what I'm offering above should seem reasonable (in rare scenarios). – Roamer-1888 Jun 27 '15 at 13:42
  • I just think you just shouldn't do both. Either you return a deferred that exposes the resolver functions as methods and the associated promise (as it is sometimes convenient indeed), *or* you use the standard practise of passing separate `resolve` and `reject` function to the callback. If you really want to do both deferreds and a callback, please at least don't mix the approaches, and follow jQuery's example of passing the deferred to the closure scope. – Bergi Jun 27 '15 at 13:50
  • Well that's all food for thought and you do seem to stop short of saying that what I'm offering here is wrong. – Roamer-1888 Jun 27 '15 at 14:22
  • Yes, after you've fixed the mistake with the `.promise()` method, the solution no more *wrong*, but I still do think that it is not useful. It goes against my subjective idea of good practises. – Bergi Jun 27 '15 at 14:31
  • Where is the `promises` variable declared? What it holds? – Marecky Nov 29 '18 at 12:15
  • @Marecky, the [demo](http://jsfiddle.net/7e3c5r1e/) should be enough to explain that. – Roamer-1888 Nov 29 '18 at 16:22
3

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();
Dharman
  • 30,962
  • 25
  • 85
  • 135
Salih Kavaf
  • 897
  • 9
  • 17
  • Quite simple and genius! Thank you! This also does not require any temporary variables like `deferreds` mentioned in some other answers. – Artfaith Dec 09 '22 at 10:47
0

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));
Dmitriy Mozgovoy
  • 1,419
  • 2
  • 8
  • 7