1

I have a bunch of functions used to provide data to my service. I want to loop through each of them and stop as soon as one of them returns the desired result. If the first one works, thats fine. If it has an exception or data is not valid, I would like to move to the next one and so on.

How may I achieve this? I have the below code:

handleData: function(address) {
  var self = this;
  return new Promise(function (resolve, reject) {
    for (var i = 0; i < self.listAllAvailableProviders.length; ++i) {
      var handler = self.listAllAvailableProviders[i];
      new handler().getData(address)
        .then(function(value) {
          Logger.info(value);
          resolve(value);
        })
        .catch(function(err){
          Logger.error(err);
        })
    }
    reject("");
  });
}

how can I fix it to stop as soon as the first one gets the right data? I have read through the bluebirdjs documentation to no avail.

EDIT I put a break statement after resolve and I got this:

SyntaxError: Illegal break statement
at Object.exports.runInThisContext (vm.js:53:16)
at Module._compile (module.js:513:28)
at Object.Module._extensions..js (module.js:550:10)
at Module.load (module.js:458:32)
at tryModuleLoad (module.js:417:12)
at Function.Module._load (module.js:409:3)
at Module.require (module.js:468:17)
at require (internal/module.js:20:19)
KVISH
  • 12,923
  • 17
  • 86
  • 162
  • You are running all your requests in parallel in the `for` loop. By the time you find a result you like, all the other requests have already been started. You could serialize your requests one at a time and then only proceed with the next request if you didn't find an answer you like. Is that what you want? – jfriend00 Aug 03 '16 at 01:01
  • Can you further explain the serialize one by one? I want the first one that succeeds, so if the one doesn't work then try the next one. This is using `sailsjs`. – KVISH Aug 03 '16 at 01:02
  • Explain what? I don't know what part of my comment you did not understand. Do you want to serialize your requests (run them one at a time)? – jfriend00 Aug 03 '16 at 01:02
  • Yes, that would work. But I'm in a position where everything is running with promises... – KVISH Aug 03 '16 at 01:03

2 Answers2

2

You are running all your requests in parallel in the for loop so when you find one that has a value you like, the others have already been started so there's no way to "not" run them. If you want to not run the others once you've found one, you need to not start them in parallel. So, that would lead you to a design pattern where you serialize the requests. Run one, if it doesn't succeed, run the next and so on.

As best I can tell, there isn't a built-in scheme in Bluebird for doing what you're asking. The simplest thing I can think of is to use one of the array processing functions in Bluebird that will serialize the requests one after the other such as Promise.mapSeries() and then use a rejection to abort the processing when you found a good value.

handleData: function(address) {
    return Promise.mapSeries(this.listAllAvailableProviders, function(handler) {
        return new handler().getData(address).then(function(value) {
            // the first success we get, we will throw with 
            // the returned value in order to stop the .mapSeries progression
            throw value;
        }, function(err) {
            // log the error, but don't let the rejection propagate so other handlers are called
            Logger.error(err);
        })
    }).then(function() {
        // nothing succeeded here, turn it into an overall rejection
        throw new Error("No getData() handlers succeeded");        
    }, function(val) {
        // reject here means we got a good value so turn it into a resolved value
        return val;
    })
}


// usage
obj.handleData().then(function(val) {
    // got value here
}).catch(function(err) {
    // no values here
});

Curiously enough, it seems to be less code and perhaps a bit simpler if you just iterate the handlers yourself:

handleData: function(address) {
    var index = 0;
    var handlers = this.listAllAvailableProviders;
    var handlerCnt = handlers.length;

    function next() {
        if (index < handlerCnt) {
            var handler = handlers[index++];
            return new handler().getData(address).catch(next);
        } else {
            return Promise.reject(new Error("No handler found for address"));
        }
    }
    return next();
}
jfriend00
  • 683,504
  • 96
  • 985
  • 979
0

if promises are not a hard constraint caolan/async#eachSeries or similar might help. Something like...

// var Promise = require(?)
// var async = require("async")
handleData: asyncProviderFinder

...

function asyncProviderFinder(address){
  var self = this;
  return new Promise(function(resolve, reject){
    async.eachSeries(
      self.listAllAvailableProviders, 
      function iterate(provider, next){
        var handler = provider;
        new handler().getData(address)
        .then( function(value){
          Logger.info(value);
          next("abort"); // callback any error to abort future iterations
          return resolve(value);
        })
        .catch( function (err){
          Logger.error(err);
          next();
        });
      },
      function callback(err, firstProvider){
        if ((firstProvider === undefined) && !err ){ reject(""); }
      }
    );
  });
}
Plato
  • 10,812
  • 2
  • 41
  • 61
  • not familiar with that library. But your code doesn't even run when I install the library and `var async = require("async");`? – KVISH Aug 03 '16 at 01:23
  • I get the error: `return new Promise(function resolve, reject) { ^ SyntaxError: Unexpected token , at Object.exports.runInThisContext (vm.js:53:16) at Module._compile (module.js:513:28)` – KVISH Aug 03 '16 at 01:24