1

Thanks in advance everyone.

I am using ES6 native promises for the first time (in the past, I've used Q or Angular's $q service). I have a problem that would be easily solved by using a deferred, and was surprised to discover that ES6 promises don't use them.

I was further surprised to discover a large number of people on blogs referring to the usage of deferred as an anti-pattern. Even after reading a few blogs decrying their use, I still don't really understand why - their examples were largely places where I never would have used a deferred to begin with so maybe I'm missing something (and wouldn't mind an explanation of why they are bad as an aside). But in the interests of remaining within the confines of best practices, I'm trying to figure out how to solve something without pulling in a library that supports them, and I'm stuck.

I am using the OpenLayers library to create a map in my React/Redux application. OpenLayers requires a DOM element to place the map on when the map object is created. Therefore I cannot instantiate the OpenLayers map until the component has mounted. That's fine, there's a lifecycle hook for that with componentDidMount.

Where I run into a problem is that I need to expose that map object to other classes and those classes have code that needs to wait to run until the map has been created.

With a deferred, I could handle this fairly simply as shown below:

const deferred = Q.defer();

const Foo = React.createClass({
  componentDidMount() {
    const map = new ol.Map(...);
    deferred.resolve(map);
  }
  ...
}

export getMap => deferred.promise

But with ES6 promises, I'm stuck.

I can't instantiate the promise outside of the componentDidMount class because it will fire immediately.

If I instantiate a promise (or use Promise.resolve since my code is synchronous) within the scope of the componentDidMount function, the promise is out of scope for the getMap function.

If I declare but do not instantiate the promise object outside of scope of componentDidMount, getMap would return undefined until componentDidMount executes, defeating the point of having a promise in the first place.

As a final thought, I am not putting the map in the Redux state because OpenLayers maps are highly mutable and do their own thing, it would break the Redux commitment to immutability if I put the map in there... so I've abandoned Redux solutions. Throwing some kind of boolean on the Redux state like "mapAvailable" feels like a hack. I could probably make that work but it would introduce a host of secondary problems.

So what am I missing? Am I in a corner case where deferreds are really the right answer or am I just not seeing the obvious?

Again, thank you for your time.

JSager
  • 1,420
  • 9
  • 18

2 Answers2

4

I was further surprised to discover a large number of people on blogs referring to the usage of deferred as an anti-pattern.

You seem to refer to the deferred antipattern here, which is about a specific usage of deferreds - it doesn't say that deferreds are an antipattern.

Although deferreds are deprecated nonetheless :-) You don't need any deferred object, the resolve function is absolutely enough.

I can't instantiate the promise outside of the componentDidMount class because it will fire immediately.

Why do you fire it immediately? Just don't do that. You could even wrap it around the class:

export const promise = new Promise(resolve => {
  const Foo = React.createClass({
    componentDidMount() {
      const map = new ol.Map(…);
      resolve(map);
    }
    …
  }
  … // do something with Foo here
});

but more likely you will want to do

let resolve;
export const promise = new Promise(r => {
  resolve = r;
});
export const Foo = React.createClass({
  componentDidMount() {
    const map = new ol.Map(…);
    resolve(map);
  }
  …
}

Of course notice that this is a horrible pattern - you've just created a global, static promise, a singleton basically. It will be resolved when any of the Foo components you might have in your app will get mounted. Don't do that. I would recommend to put the promise in your component's constructor, although I'm still not sure what exactly you need it for.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 1
    I think this resolves both of my questions. I wasn't thinking about "resolve" properly. I'm watching you edit your response live as you think more about it, and I appreciate you bending additional thought to my question. In terms of whether this is a horrible pattern, there will be one and only one Foo component in the application, so singleton is relatively appropriate. In terms of what I need it for, many other classes throughout the application will need to access the one and only copy of the OpenLayers map object. Does it still seem like a bad decision? – JSager Nov 21 '17 at 16:35
  • I think yes, it would still be a bad idea to put the promise as an indendent singleton from the `Foo` singleton - wherever you instantiate *that* in your app, the promise for its map should be instantiated as well. So just make it a plain property of `Foo` – Bergi Nov 21 '17 at 16:49
  • A few problems I foresee with that. First, I don't really have control over when React chooses to instantiate my component. Second I'm not exporting the React component. I'm exporting a React-Redux component elsewhere in the code that I did not include because it didn't seem relevant to my problem, and wrapping the entire React class in a promise would create new scoping issues. Finally, as I explained initially, I can't put the promise in the constructor of the React component because it has to hit the "mounted" part of its lifecycle before I can safely instantiate the map. – JSager Nov 21 '17 at 17:09
  • I meant to create the promise in the constructor, but still resolve it when the component gets mounted. That said, I don't know enough about (your) React architecture to give good advise. – Bergi Nov 21 '17 at 17:11
0

You are in a case where deferred will work fine, but they are made obsolete and that is the reason you are finding the blogs that states the usage of deferred as anti-pattern.

You can take a look at this link that will give you more details that deferred was made obsolete.

I think of the same solution as mapAvailable and agree that it would be a hack. Let us know if you find a better solution for this situation.

Brijesh Bhakta
  • 1,280
  • 1
  • 8
  • 14