0

Is there a way using javascript native promises (docs) to create a promise and attach thenables, without knowing at constructor time how it will resolve?

var foo = new Promise(function(resolve, reject) {
    // I don't know how this will resolve yet as some other object will resolve it
});

foo.then(function(val) {
  console.log("first " + val);
});

foo.resolve("bar");

foo.then(function(val) {
  console.log("second " + val);
});

// result
// first bar
// second bar
Owen Allen
  • 11,348
  • 9
  • 51
  • 63
  • If it's an anti-pattern then `$(function() {}` in jQuery is an anti-pattern, since I'm asking for the same functionality except instead of being only a single event (dom ready), it can be based on ad-hoc events: Register functions to be called on an event, if functions are registered after the event is called, they will be called immediately with the same arguments. If you can figure out some way to do that without the "anti-pattern" then please share it. Simply saying "you don't really need that" doesn't help anyone and is like saying we don't really need `DOM ready`. – Owen Allen May 02 '14 at 21:09

2 Answers2

5

Simply save them inside of a closure.

var makePromise = function () {
    var resolvePromise = null,
        rejectPromise  = null,

        promise = new Promise(function (resolve, reject) {
            resolvePromise = resolve;
            rejectPromise  = reject;
        });

    return { promise : promise, resolve : resolvePromise, reject : rejectPromise };
};


var deferredSomething = function () {
    var deferredThing = makePromise();
    waitAWhile()
        .then(doStuff)
        .then(function (result) {
            if (result.isGood) {
                deferredThing.resolve(result.data);
            } else {
                deferredThing.reject(result.error);
            }
        });

    return deferredThing.promise;
};

This is actually the majority of the difference between the "deferred" concept and the "promise" concept; one more level, on top, that has the actual remote-controls that you can give to someone else, while you hand the .then|.success|.done|etc... to your consumers.

Once you bring those functions out into your upstream process, you can happily lazy-load whatever you'd like, using the "thenable" which you'll return, and then succeed or fail your chain (or leave it hanging) at will...

UPDATE

Seeing as this is probably going to continue to be the chosen answer, and continue to be voted down, as the solution to the exact problem he was having (ie: retrofitting code which was not made with ES6 promises in mind), I figure I'll add a more detailed example of exactly why using this antipattern selectively can be better than naught:

MongoClient.connect("mongodb://localhost:21017/mydb", (err, db) => {
    db.collection("mycollection", (err, collection) => {
        collection.find().toArray((err, results) => {
            doStuff(results);
        });
    });
});

If I were to write a library, here, hoping to reach the point where I could write:

let dbConnected = MongoClient.connect(dbURL);

dbConnected
    .then(db => db.collection(myCollection))
    .then(collection => collection.find(query))
    .then(stream => doStuff(stream));

...or alternatively:

composeAsync(
    (stream) => doStuff(stream),
    (collection) => collection.find(query),
    (db) => dbCollection(myCollection)
)(dbConnected);

...for ease of use within the library, does it make sense to wrap every single function-body within an instantiated promise // find = curry(query, collection) return new Promise(resolve, reject) { /* whole function body, here / / do lots of stuff which is irrelevant to the resolution of mongo.db.collection.find, but is relevant to its calling */ collection.find(query).toArray( /node-callback/(err, result) { if (err) { reject(err); } else { resolve(result); } }); };

...or in looking at the pattern of really only requiring the node-specific callback to be resolved, does it make more sense to have some form of promise-resolver, to save having to write out / copy-paste a half-dozen purely-redundant lines which should be completely DRYed up?

// find = curry(query, collection)
let resolver = new NodeResolver();
collection.find(query).toArray(promise.resolve);
return resolver.promise;

Yes, that is an anti-pattern... ...yet, an antipattern which requires fewer keystrokes, restores the natural flow of the promise-chain, fixes a problem with Node's callback-only API, reduces the potential for errors, et cetera.

Yes, there are already libraries which do this... ...solutions specific to X library or Y... ...or solutions which globally override methods of various modules (scary) ...or solutions which, again, basically force you to pass in all of the details of the call you're making:

wrapNodeMethod(fs, "read", url, config).then(data => { /*...*/ });

But there is no simple solution for the case of inverting all of that pain, without either:

a) wrapping the entire function body in a promise, to feed the async callback a resolver b) using an antipattern within a library, in order to pass Node callbacks a resolver that the rest of the function-body needs to know precisely nothing about.

Even if data needed to be transformed within the resolver, it would still make more sense to return a transformed set, in a new promise

let resolver = new NodeResolver();
somethingAsync(resolver.resolve);
return resolver.promise.then(transformData).then(logTransform);

...than to wrap the whole body, including transforms, etc, just for closure-scope reference, just for the sake of avoiding an "antipattern" which clearly goes against the grain of what has become a very prominent JS platform/paradigm.

Now, personally, I'd be happier if IO||Node methods returned a promise and/or a stream, as well as taking a callback, as a core part of the platform... ...that's not going to happen...

...but you can't possibly tell me that writing less, and keeping Node modules DRY, while still using ES6 Promises is an "antipattern", without providing me a more-eloquent solution, therefore.

If you can, indeed, provide me something that I can use in any NodeJS callback, which does, indeed, provide a more eloquent solution to this, such that I don't have to wrap every body of every method which contains an async callback in a new constructor, or use clunky dispatcher methods, or hijack entire modules to override their global functionality...

...I'd be more than willing to retract my statement that this particular pattern is still highly-useful, as regards interfacing with APIs which are prone to pyramids o'dewm.

Norguard
  • 26,167
  • 5
  • 41
  • 49
  • 1
    Works perfectly. I renamed your `makePromise` to `makeDeferred` in my own code. I also trimmed the cruft out of my post to make it easier for future readers. – Owen Allen Feb 19 '14 at 07:12
  • 1
    Please stop using promises like event emitters, how do you people even justify using promises if you write code like this with them? `deferredSomething` should simply be `return waitAWhile().then(doStuff);`, not this busy code running in circles doing nothing – Esailija Feb 20 '14 at 06:37
  • 1
    @Esailija he asked for an answer, specifically for the purpose of retrofitting old code, which sounded expressly like he needed, in the context of the old code, to separate the logic of the resolution from the flow of the code (specifically the point of its construction). And when you have a 500-line, legacy module which uses a format which makes it difficult (or impossible) to do all resolution in the constructor, without refactor, then these are the things you get. I didn't say it was the ***right*** way to do it, merely provided a solution which can be retrofit to what he was looking for. – Norguard Feb 23 '14 at 02:20
  • 1
    @Norguard no I mean your code is like writing `if (b === true) { return true; } else { return false; }` for which there is no other explanation other than "I don't understand booleans". – Esailija Feb 23 '14 at 10:37
  • 1
    @Esailija well, that's fine. The point I was trying to make, however, is that if he wants to delegate the resolution of the promise to a process which is not (or can not) be defined in the constructor (legacy/etc), then they can be stored in a parent scope, and exported with the promise, and then they can be given to anyone. They could even be resolved in separate logic chains (not a great idea). The `if (true) { success(); }` is just a contrived example of how to fire a success/failure outside of the constructor. They could happily be callbacks, or any logic gate system you want to build. – Norguard Feb 23 '14 at 11:15
5

If result of promise depends on other promise, you should just create a promise using then.

What was proposed by @Norguard in direct form, doesn't make much sense (it's even coined as deferred anti-pattern). Below code does exactly same, and no extra promise is needed:

var deferredSomething = function () {
  return waitAWhile()
      .then(doStuff)
      .then(function (result) {
          if (result.isGood) {
            return result.data;
          } else {
            throw result.error;
          }
       });
  });
};

And, even if, for whatever reason you would need to create promise upfront, then with constructor pattern it would be cleaner to do it that way:

var deferredSomething = function () {
  return new Promise(function (resolve, reject) {
    waitAWhile()
      .then(doStuff)
      .then(function (result) {
          if (result.isGood) {
            resolve(result.data);
          } else {
            reject(result.error);
          }
       });
  });
};
Mariusz Nowak
  • 32,050
  • 5
  • 35
  • 37
  • I don't believe you answer would work. Could you show how you would create a promise, attach thenables, and then resolve it so that the thenables receive the resolved value? – Owen Allen Feb 19 '14 at 15:38
  • @MariuszNowak It is an anti-pattern, in terms of the modern A+ spec. The problem is that before A+ came around (or, more-aptly, before that practice was widely understood/adopted), a lot of code was written using Deferreds, with the expectation that the resolve/reject could be passed around as control-statements. An example of this might be deferring loading of a widget, until a user-interaction loads it, however, having *currently-existing* widgets subscribing to it, if ever it does arrive, or splicing done/fail/resolve/reject branches. Putting all of this in a monolithic constructor is ugly. – Norguard Feb 19 '14 at 17:23
  • @MariuszNowak and while "promises as branching control-flow" hasn't been widely-used, and rather they're used as pipes, there's still code that would want to use that functionality (without having to be completely refactored). – Norguard Feb 19 '14 at 17:25
  • @Nucleon It will work if you put *all* of your async stuff inside of the constructor. The idea of "thenables" in A+ compatible libraries is that you create a pipe with `.then`. To do so, you're doing your resolving/rejecting in the constructor, and using the `.then`. The stuff inside of the Constructor dosn't have to be ***resolved*** before you start chaining things to it. What *does* have to happen is all of the **logic** for determining ***how*** to resolve needs to be inside the constructor. – Norguard Feb 19 '14 at 17:29
  • @Norguard anti-pattern doesn't neglect construction of promises with deferred function, but construction of obsolete promises, you can do same mistake with Promise constructor. Please look carefully at my example. It's named "deferred anti-pattern" as when it was coined, construction of unresolved promises, in most libraries was made with deferred function (and that's still the case) – Mariusz Nowak Feb 19 '14 at 18:42
  • @Nucleon you can wrap your logic within function that's passed to promise constructor. Still the problem is that you don't have access to actual promise over there (some propose it should be accessible through `this`, and I hope it'll be that way). To say more I'd need to see real use case you're struggling with. It's not first request like that I see, it just shows that proposed constructor pattern is not as good as it looks. – Mariusz Nowak Feb 19 '14 at 18:52
  • The specific reason I need this so-called anti-pattern, which is debatable, is for semantics similar to the `DOM ready` event which can be triggered only once. If you register prior to trigger, it will execute on trigger. If you register after the trigger, it will still execute. It is possible the event may never be triggered. Due to the fact that functions register to an event well before it's triggered, there is simply no way to pass in the resolve pathway ahead of time. Anti-pattern or not, I think it is a useful tool. – Owen Allen Feb 19 '14 at 19:09
  • 1
    @Nucleon Anti-pattern is when you **definitely don't have to do it**. There are cases where you need to create unresolved promises manually e.g. when resolving values from streams, and that's perfectly fine. However, if you resolve values out of flow already built with promises (as in @Norguard example) and create unresolved promise to resolve it with expected value, then that's wrong, as you don't have to do that. - this is deferred anti-pattern I wanted to point (just using `deferred` function is totally fine if justified, and it's by no means an anti-pattern). – Mariusz Nowak Feb 19 '14 at 19:34
  • @Nucleon simply returning the promise on DOM ready would also work. This answer is the only sane one. The other answer promotes an anti pattern. – Benjamin Gruenbaum Mar 24 '14 at 02:03