简体   繁体   中英

Using Javascript native promises, resolve a promise after thenables attached

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

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);
          }
       });
  });
};

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM