0

I want to create a class whose duty is to poll data sources, collate information into an array of 'alert' objects, and then deliver a subset of those alerts to any other class that wants them.

Because polling happens asynchronously (I'm requesting data from a web service) then I assume that what I actually need to return is a promise which, when fulfilled, will give the correct subset of Alert objects.

But clearly I don't understand how to do this, because the method that is supposed to return the promise returns something else.

Here's my code so far. As you can see, I'm trying to store the promise in an instance attribute and then retrieve it:

export class AlertCollection {

  constructor() {
    this.alerts = null;
  }

  // poll the data sources for alert data; store a promise that resolves
  // to an array of alerts
  poll() {
    this.alerts = this.pollTeapot()
                    .then( (arr) => {this.pollDeliverance(arr);} );

  }                                                                                                  

  // return a promise that fulfils to an array of the alerts you want
  filteredAlerts(filter) {
    return this.alerts; // not filtering for now
  }                                                                                                  

  // return a promise that fulfills to the initial array of alerts
  pollTeapot() {

    let process = (json) => {
      json2 = JSON.parse(json);
      return json2.map( (a) => new Alert(a) );
    };                                                                                               

    message = new MessageHandler("teapot", "alerts")
    return message.request().then( (json) => {process(json);} );
  }

  // Modify the alerts based on the response from Deliverance.
  // (But for the time being let's not, and say we did.)
  pollDeliverance(alerts) {
    return alerts;
  }

}

message.request() returns a promise from the web service. That works. If I snapshot the process function inside pollTeapot() I get the right data.

But, if I snapshot the return value from filteredAlerts() I don't get that. I don't get null either (which would at at least make sense, although it would be wrong.) I get something like { _45: 0, _81: 0, _65: null, _54: null }.

Any pointers would be very much appreciated at this point. (This is in React Native, by the way, if that helps.)

Andy Jones
  • 1,074
  • 1
  • 10
  • 21
  • I get your question gererally, but your title metions processing 2 promises, I cant see where that is - AFAICS you're just processing one - the `message.request()` bit – Jamiec Jul 06 '17 at 11:26
  • If you want to chain promises, you should have a look at `Promise.all()` in ES6. – Hinrich Jul 06 '17 at 11:46
  • @Jamiec in `poll()` I'm chaining `pollTeapot()` and then `pollDeliverance()`; you're right, at the moment the latter isn't doing anything, but if I can't make it work for one, there is not much point in adding another yet... – Andy Jones Jul 06 '17 at 12:06
  • 1
    @AndyJones Ah, I see `pollDeliverance` makes *another* async call and updates the `Alert`'s? – Jamiec Jul 06 '17 at 12:07
  • @Hinrich -- if I understand correctly, `Promise.all()` would call both services at the same time? I don't want that. – Andy Jones Jul 06 '17 at 12:07
  • @Jamiec yes, that's the plan. – Andy Jones Jul 06 '17 at 12:08
  • So you want to wait for one Promise to fullfill, and then wait for another one, and then combine the results? And the first call has some data that you need as input for your second async call, right? – Hinrich Jul 06 '17 at 12:16
  • @Hinrich -- Sort of. The first call returns a list of "Alerts". The second call returns supplemental information about each alert, specifically, which alerts the user has interacted with. But I can't even get it to work with just the first call, so that's probably not important right now. – Andy Jones Jul 06 '17 at 12:20

2 Answers2

0

This is going to be a hard one to describe - I have a working example but it's convoluted in that I've had to "mock up" all of the async parts, and use function classes rather than the class keyword - but the idea is the same!


There are 2 parts to this answer.

  1. It does not make sense to store alerts as an instance variable. They are asynchronous, and wont exist until after the async calls have completed

  2. You'll need to chain all of your behaviour onto the initial call to poll


In general, you chain promises on to one another like this

functionWhichReturnsPromise()
      .then(functionPointer)
      .then(function(result){
           // some functionality, which can return anything - including another promise
      });

So your code would end up looking like

var alertCollection = new AlertCollection()
alertCollection.poll().then(function(alerts){
    //here alerts have been loaded, and deliverance checked also!
});

The code of that class would look along the lines of:

export class AlertCollection {

  constructor() {
  }

  // poll the data sources for alert data; store a promise that resolves
  // to an array of alerts
  poll() {
    return this.pollTeapot()
               .then(filteredAlerts)
               .then(pollDeliverance);

  }                                                                                                  

  // return a promise that fulfils to an array of the alerts you want
  filteredAlerts(alerts) {
    return alerts; // not filtering for now
  }                                                                                                  

  // return a promise that fulfills to the initial array of alerts
  pollTeapot() {

    let process = (json) => {
      json2 = JSON.parse(json);
      return json2.map( (a) => new Alert(a) );
    };                                                                                               

    message = new MessageHandler("teapot", "alerts")
    return message.request().then(process);
  }

  // Modify the alerts based on the response from Deliverance.
  // (But for the time being let's not, and say we did.)
  pollDeliverance(alerts) {
    return alerts;
  }

}

A few notes

  • filteredAlerts can do whatever you like, so long as it returns an array of results
  • pollDeliverance can also do whatever you like - if it needs to call another async method, remember to return a promise which resolves to an array of alerts - perhaps updated from the result of the async call.

I have created a JSFiddle which demonstrates this - using a simple getJSON call to replicate the async nature of some of this. As I mentioned, it is convoluted, but demonstrates the process:

Live example: https://jsfiddle.net/q1r6pmda/1/

Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • The problem here is that I need to be able to call `filteredAlerts()` multiple times to get different filters on the returned result. I don't want to have to poll the data sources each time. That's why I wanted to have a class with its own state; without that I don't think there is much point in having a class, really. – Andy Jones Jul 06 '17 at 12:24
  • @AndyJones that was my mistake, I should have changed the argument and return from your original (see updated answer). Basicaly the argument *is* the `Alert` instances - and you can filter and return that array any way you like. See my live example where ive done exactly that – Jamiec Jul 06 '17 at 12:27
  • It sounds as if you are saying that I *can't have* a class that polls once and serves up a promise multiple times. That's a hell of a limitation. (My idea was to store the promise, not the actual results.) – Andy Jones Jul 06 '17 at 12:32
  • @AndyJones Im not sure I follow. You certainly can cache the results of a promise, but dont expect it to be there before the async has resolved. You'll be treading very close to a favourite dupe-close question round here https://stackoverflow.com/questions/6847697/how-to-return-value-from-an-asynchronous-callback-function Anyway, this is a tricky one to answer, and quite complex. Feel free to ping me on chat if you want to! (Just out for lunch so might not respond for ~20mins) – Jamiec Jul 06 '17 at 12:34
0

I am not sure if I understood your problem fully, but I will try to give you an generic solution to chaining promises one after another.

someAsyncFunction().then(dataFromAsync1 => {
    return anotherAsyncFunction(dataFromAsync1).then(dataFromAsync2 => {
        return doSomethingWithData(dataFromAsync1, dataFromAsync2);
    });
});
Hinrich
  • 13,485
  • 7
  • 43
  • 66
  • 1
    you need to remember to `return` from your callbacks inside `then` otherwise you wont be able to chain things on – Jamiec Jul 06 '17 at 12:24