2

lets suppose I have the following:

var myurls = ['http://server1.com', 'http://server2.com', 'http:sever2.com', etc ]

Each url is a "fallback" and should be used only if the previous one cannot be reached. In other words, this list specifies a priority. Lets also assume that this list can be of any length - I don't know and must iterate.

How do I go about writing a function, lets say "reachability" that loops through this array and returns the first reachable server?

I can't do $http.all as it is parallel. I can't run a while loop with an $http.get inside because the result may come later and in the mean time, my UI will freeze.

Please note I am not using jQuery. I am using ionic, which has a version of jQuery-lite in it.

Various examples I've seen talk about chaining them in .then, which is fine if you know the # of URLs before hand, but I don't.

thanks

sp00m
  • 47,968
  • 31
  • 142
  • 252
user1361529
  • 2,667
  • 29
  • 61

2 Answers2

2

Just reduce over the array:

myurls.reduce((p, url) => p.catch(() => http.get(url).then(() => url)),
              Promise.reject());

Flow explained:

It's based off the perhaps more common pattern of using reduce to build a promise chain, like so: [func1, func2].reduce((p, f) => p.then(f), Promise.resolve()); is equivalent to Promise.resolve().then(func1).then(func2) (the last arg of reduce is the initial value).

In your case, since you're retrying on failure, you want to build a retry (or reject) chain, so we must start with Promise.reject() instead. Promise.reject().catch(func1).catch(func2)

Community
  • 1
  • 1
jib
  • 40,579
  • 17
  • 100
  • 158
  • I never knew a reduce existed. While your solution looks great, I must admit my JS skills are too basic to comprehend it fully. Mind telling me the flow here ? Specifically, where do I read up about what ``()`` and ``=>`` do ? – user1361529 Mar 19 '16 at 17:39
  • 1
    It's an [arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions), just a shorthand in this case. Replace `x => x*x` with `function(x) { return x*x; }` if you will. – jib Mar 20 '16 at 02:47
  • Thank you. It looks like I can only accept 1 answer, not 2. Since I am using sp00m's solution, I'd like to keep that as my accepted choice. I learned many new things with your answer. – user1361529 Mar 23 '16 at 13:52
  • No worries, just wanted to note there are cases where recursion is the *only* solution (where length truly is unknown ahead of time), but this isn't one of them. – jib Mar 23 '16 at 15:28
1

I guess recursion and chaining could suit your needs:

var findFirstReachableUrl = function (urls) {
  if (urls.length > 0) {
    return $http.get(urls[0]).then(function () {
      return urls[0];
    }, function () {
      return findFirstReachableUrl(urls.slice(1));
    });
  } else {
    return $q.reject("No reachable URL");
  }
}

Call:

findFirstReachableUrl(myurls).then(function (firstReachableUrl) {
  // OK: do something with firstReachableUrl
}, function () {
  // KO: no url could be reached
});
sp00m
  • 47,968
  • 31
  • 142
  • 252
  • 1
    Thank you very much. This works very well. I had started writing a recursive routine but your code was much more elegant. – user1361529 Mar 16 '16 at 18:30
  • I was relooking at this code and wondering why we don't need to do a return ``$q.resolve(urls[0])`` in line 4 and just ```return urls[0]``` works? – user1361529 May 01 '16 at 12:46
  • 1
    @user1361529 Because the `then` method returns a `Promise`, see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then#Chaining. – sp00m May 03 '16 at 08:03