Using Javascript native promises, resolve a promise after thenables attached
Asked Answered
L

2

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
Larimor answered 18/2, 2014 at 22:36 Comment(1)
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.Larimor
K
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.

Karrikarrie answered 18/2, 2014 at 22:42 Comment(5)
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.Larimor
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 nothingCartouche
@Cartouche 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.Karrikarrie
@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".Cartouche
@Cartouche 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.Karrikarrie
T
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);
          }
       });
  });
};
Tamikatamiko answered 19/2, 2014 at 11:26 Comment(9)
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?Larimor
@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.Karrikarrie
@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).Karrikarrie
@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.Karrikarrie
@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)Tamikatamiko
@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.Tamikatamiko
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.Larimor
@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).Tamikatamiko
@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.Regressive

© 2022 - 2024 — McMap. All rights reserved.