2

I'm pulling data from 3 differents APIs, and I want to merge all these results into one array.

I guess the proper way to do this is to use Promises:

var function1 = new Promise((resolve, reject)=>{
    ...
    resolve();
});
var function2 = new Promise((resolve, reject)=>{
    ...
    resolve();
});
var function3 = new Promise((resolve, reject)=>{
    ...
    resolve();
});

Promise.all([function1, function2, function3]).then(function(values){
    // Values are all here!
});

How can I call all the promises again and join them via Promise.all every second?

I've tried

setInterval(function(){
    Promise.all([function1, function2, function3]).then(function(values){
        // Values are all here and up to date!
    });
}, 1000)

without success.

Thanks for your help!

Vico
  • 1,696
  • 1
  • 24
  • 57
  • You can push it in a single array with a global variable – praveenkumar s Jan 31 '18 at 16:16
  • Possible duplicate of [Resolve a chain of promises with timeouts. Promise.all](https://stackoverflow.com/questions/46609804/resolve-a-chain-of-promises-with-timeouts-promise-all) – ponury-kostek Jan 31 '18 at 16:40
  • 1
    First off... don't do this in a setInterval... not unless you want to increase the delay to 10seconds or more. Otherwise you're just gonna end up with a backlog of requests pending. – Kevin B Jan 31 '18 at 16:45
  • You are right thank you @KevinB – Vico Jan 31 '18 at 17:19

6 Answers6

5

You need to recreate the Promise objects every time you want to invoke them:

var function1 = (resolve, reject)=>{
    console.log('calling 1');
    resolve();
};
var function2 = (resolve, reject)=>{
    console.log('calling 2');
    resolve();
};
var function3 = (resolve, reject)=>{
    console.log('calling 3');
    resolve();
};

setInterval(function(){
    Promise.all([new Promise(function1), new Promise(function2), new Promise(function3)]).then(function(values){
        console.log('alldone');
    });
}, 1000)

This is because the promise is only executed upon creation, and otherwise in your loop you're just attaching a new then() method which will not call your API.

EDIT: Be advised that setInterval, as shown, will fire three requests to your API every 1 second. That's a pretty fast rate of fire and is likely to get you in trouble unless both your API and the network are blazing fast. A more sensible approach might be to only fire the next request once the previous one has been handled. To do that, simply substitute the setInterval call with this:

var callback = function(){
    Promise.all([new Promise(function1), new Promise(function2), new Promise(function3)]).then(function(values){
        console.log('all done');
        setTimeout(callback, 1000);
        console.log('next call successfully enqued');
    });
};
setTimeout(callback, 1000);

Thanks to Kevin B for pointing this out.

Demonblack
  • 384
  • 2
  • 8
  • It's working! Thank you. Is it something we could consider as "clean"? – Vico Jan 31 '18 at 16:50
  • I suggest you to edit your answer to add updates suggested by @Kevin B. It should be in a setTimeout instead of a setInterval – Vico Jan 31 '18 at 16:55
  • Did that, it's much cleaner now. I was actually at work when I replied and I didn't really stop to think before replying, lol. – Demonblack Jan 31 '18 at 23:00
1

Make sure you call the API each time (by creating new Promise).

/**
 * Create function that return a NEW Promise each time.
 * Once resolved a promise won't change state
 */
const function1 = () => new Promise((resolve, reject)=>{
    // something
    resolve('1 - ' + new Date());
});
const function2 = () => new Promise((resolve, reject)=>{
    // something
    resolve('2 - ' + new Date());
});
const function3 = () => new Promise((resolve, reject)=>{
    // something
    resolve('3 - ' + new Date());
});

/**
 * For the setInterval callback, create a function 
 * that will return a new Promise from all the previous promises
 */
const all = () => Promise.all([
    function1(),
    function2(),
    function3()
]).then(function(values){
    console.log(values);
    return values;
});

setInterval(all, 1000);
topheman
  • 7,422
  • 4
  • 24
  • 33
0

Answer

This question was in my interview and I had trouble that it should be implemented only using class, maybe you will find it useful for you and rewrite it using functions.

class HTTPService {
    constructor(base = "", strategy = "default" | "queue", promises = []) {
      this.base = base;
      this.strategy = strategy;
      this.promises = 0;
    }
    urlSerializer(payload) {
      return `?${Object.entries(payload)
             .map((el) => el.join("="))
             .join("$")}`;
    }
    returnDefaultPromise(path) {
      return new Promise((resolve) =>
          setTimeout(() => {
              resolve(path);
          }, 1000)
      );
    }
    returnQueuePromise(path) {
      return new Promise((resolve) =>
        setTimeout(() => {
          this.promises -= 1000;
          resolve(path);
        }, this.promises)
      );
    }
    get(url, payload) {
      let serialized = payload ? this.urlSerializer(payload) : "";
      if (!url) throw new Error("Please add url to function argument");
      switch (this.strategy) {
        case "default":
          return this.returnDefaultPromise(`${this.base}/${url}${serialized}`);
        case "queue":
          this.promises += 1000;
          return this.returnQueuePromise(`${this.base}/${url}${serialized}`);
        default:
          return new Promise((resolve) =>
              resolve(`${this.base}/${url}${serialized}`)
            );
        }
    }
}

const httpService = new HTTPService("http://test.com", "queue");
const httpService2 = new HTTPService("http://test.com", "default");
const br = document.createElement('br');
let div = document.createElement('div');
let content = document.createTextNode('');
httpService.get("/api/me").then((data) => {
  content = document.createTextNode(data);
  div.appendChild(content);
  div.appendChild(br);
  console.log(data);
});
// should be 'http://test.com/api/me'
// B:
httpService.get("/api/test", { foo: 1, test: 2 }).then((data) => {
  content = document.createTextNode(data);
  div.appendChild(content);
  div.appendChild(br);
  console.log(data);
  // should be 'http://test.com/api/test?foo=1&test=2'
});
// C:
httpService.get("/api/test", { baz: 10, case: "some" }).then((data) => {
  content = document.createTextNode(data);
  div.appendChild(content);
  div.appendChild(br);
  console.log(data);
  // should be 'http://test.com//api/test?baz=10$case=some'
});
// D:
httpService.get("/api/test", { bar: 1, dummy: "text" }).then((data) => {
  content = document.createTextNode(data);
  div.appendChild(content);
  div.appendChild(br);
  console.log(data);
  // should be 'http://test.com//api/test?bar=1$dummy=text'
});
httpService2.get("/app/test").then((data) => {
  content = document.createTextNode(data);
  div.appendChild(br);
  div.appendChild(content);
  div.appendChild(br);
  console.log(data);
});
document.querySelector('#result').appendChild(div)
<div id="result"></div>

This example to call functions

Also you can check analogy in react application through codesandbox

Otabek Butcher
  • 545
  • 8
  • 19
-1

Solution

Every time you need to create promises again and resolve them.

setInterval(function(){
    var function1 = new Promise((resolve, reject)=>{
        resolve(new Date());
    });
    var function2 = new Promise((resolve, reject)=>{
        resolve(2);
    });
    var function3 = new Promise((resolve, reject)=>{
        resolve(3);
    });
    Promise.all([function1, function2, function3]).then(function(values){
        console.log(values);
    });
}, 1000)

Run the above code its working!!!

Community
  • 1
  • 1
Tilak Putta
  • 758
  • 4
  • 18
  • This code won't call his API every 1 second, it will just `console.log` that it has done so without actually doing it. The API only gets called once. – Demonblack Jan 31 '18 at 16:25
-1

An alternative to setInterval() is using setTimeout() inside a recursive loop:

function joinedResults() {
    Promise.all([function1, function2, function3])
        .then(function(values){
            console.log(values);
            setTimeout(()=>{ joinedResults() }, 1000);
        });
}

joinedResults();
  • This code will only log the first result resolved. Any API calls done after won't be taken in account. – topheman Jan 31 '18 at 16:39
-1

This stackoverflow post has a similar question.

In order to chain your promise outside of setInterval, you can wrap it in a function:

let repeat = (ms, func) => new Promise(r => (setInterval(func, ms), wait(ms).then(r)));

repeat(1000, () => Promise.all([myfunction()])
      .then(...)
Nishnha
  • 323
  • 2
  • 4