5

I created this little helper to expose resolve and reject outside of the Promise's constructor

export function createPromise() {
    let resolve,reject;
    let promise = new Promise((r,j) => {
        resolve = r;
        reject = j;
    });
    Object.assign(promise,{resolve,reject});
    return promise;
}

Because sometimes it's just really awkward to wrap your entire script in

new Promise((resolve,reject) => { 
   // hundreds of lines of code, most which have nothing 
   // to do with this Promise but I need the Promise object 
   // at the top level somewhere so that I can expose it, 
   // but it won't be resolved until some deeply nested construct is hit  
})

Or to give a more concrete example:

let p1 = kb.createPromise();
let p2 = kb.createPromise();

Promise.all([p1,p2]).then(() => {
    $('#calendar-bookings').scrollIntoView({
        duration: 200,
        direction: 'y'
    });
});

$('#bookings-table')
    .dataTable(getOptions(dtSourceUrl, {date, driverOptions}))
    .one('draw.dt', () => p1.resolve());

ReactDOM.render(<VehicleTimeline start={dateMom.toDate()} onLoad={() => p2.resolve()}/>, document.getElementById('vehicle-timeline'));

This way I also don't have to worry about whether resolve() is called synchronously before I even get a chance to bind my .then. [I stand corrected, the .then will fire immediately] I think this is pretty clear: create two promises, bind the .then, and only after is it even conceivable that they're resolved.

Sure, this would allow anyone who you've passed your Promise off to to resolve it (i.e. giving control away from the Promise creator and to the consumer) but if the consumer fires it early, that's their loss because presumably they're the ones interested in the event, no?

Also, this would give the consumer the ability to reject() the Promise which they could abuse as a sort of Promise cancellation. I'm not saying that's a good idea, but I don't think the extra freedom is necessarily bad either.

Is there anything else I'm missing? Any problems with my createPromise method?

mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • 1
    Why don't you simply use [`Promise.resolve()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve) (and reject)? That way you can bypass the whole issue without this awkward construct. – Etheryte Oct 13 '17 at 21:05
  • @Nit That creates a new Promise that's already resolved. I'm not sure how that helps? – mpen Oct 13 '17 at 21:11
  • 1
    This seems to be pretty close to top "related" question, [Resolve Javascript Promise outside function scope](https://stackoverflow.com/q/26150232/1426891), which argues that your type of function is rarely-needed but effective. Yours is much more elegant, though! Marking as dupe, but feel free to ignore/edit if yours has different constraints/concerns. – Jeff Bowman Oct 13 '17 at 21:19
  • 1
    You can do it, but it makes flow less obvious and can be pretty confusing. Plus with decoupling resolve/reject functions from corresponding promise you need to handle exceptions separately twice. – dfsq Oct 13 '17 at 21:21
  • Why do you " worry about whether resolve() is called synchronously before I even get a chance to bind my .then"? The `then` method can be called before or after a promise becomes settled without changing the effect... – traktor Oct 13 '17 at 21:22
  • This is discussed here: [When would someone need to create a deferred](https://stackoverflow.com/questions/32853105/when-would-someone-need-to-create-a-deferred/32857145#32857145). – jfriend00 Oct 13 '17 at 21:24
  • "*hundreds of lines of code, most which have nothing to do with this Promise*" - there's your actual problem. Just don't put those inside the promise constructor. Construct the promise only in the one place where you actually are going to resolve it (in your example, around the `.one` and `.render` calls), and if you really have "*some deeply nested construct*" then use functions that *`return`* the promise they constructed deep in their bowels. – Bergi Oct 13 '17 at 22:38
  • The downside is maintainability. Formulated pointedly, you have a messy spaghetti code structure and you need to affect a promise that is 200 lines away. Your question "What's wrong with exposing the resolver functions?" is essentially the same as "What's wrong with just introducing a global variable for a callback that I can invoke from anywhere?". Yes, this will work, and not much work to implement, but the proper solution is obviously to fix the architecture. – Bergi Oct 13 '17 at 22:44
  • @Traktor53 Oops... you're quite right. The `.then` will just fire immediately. I thought I'd have "missed" the event. – mpen Oct 13 '17 at 23:37

1 Answers1

1

This is a variation of the deferred pattern except that you return the promise object with the resolve/reject functions in the promise object. The original deferred pattern created an object with the promise and the resolve/reject functions separately. So you can pass out the promise without exposing the controls.

To be honest, there are very little places where actually breaking out of the constructor scope is needed. In my opinion it's a little bit easier to make a mistake with this pattern and end up with unresolved promises than with the constructor pattern. As far as I can tell really, the only benefit from the constructor pattern over the deferred pattern that you get is that you can throw and reject the promise immediately (through a sync mistake).

MinusFour
  • 13,913
  • 3
  • 30
  • 39