0

Inside a promise, I need to call and process an indeterminate number of asynch API responses after individually calling them either inside another promise, or after said promise, but before another so the order of execution is respected.

var promiseA = function() {
  return new Promise(function(resolve, reject) {
  // 1. Establish objects needed from one API endpoint
  // 2. Call API endpoint for each object and parse
  // 3. Only then continue to next promise
  }
}

var finalPromise = function() {
  return new Promise(function(resolve, reject) {
  // 
  }
}

promiseA()
.then(finalPromise)

So inside promiseA, I find out how many objects I'll need to poll individually from an API. Each request is of course asynchronous. I need to make these calls and process the response before the final promise is called.

I am struggling to determine a pattern for this with promises, where I can dynamically create these promises and only allow the final promise to execute after the indeterminate and asynchronous have executed and processed. I've worked with other languages where this is possible, but I'm struggling to see it here with Promises.

Any help is appreciated.

  • In any case, every "call API endpoint" should return a promise itself, and `promiseA` should not use the [`Promise` constructor antipattern](https://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it). – Bergi Aug 30 '17 at 17:44
  • It's unclear what exactly your code should be doing. We can't you give you anything more than a generic `function promiseA() { return Promise.all(establishEndpointsArr().map(callEndpoint)); }` – Bergi Aug 30 '17 at 17:45

3 Answers3

0

I have changed the answer to incorporate the comments below. Since, you mentioned ES6 promises I shall stick to that. There are two basic types of callbacks that we might care about.

  1. DOM load or other one time event callbacks (window.onload and so on)
  2. Async method callback (AJAX call, setTimout and so on)

Since,

1.DOM load or other one time event

var p = new Promise(function(res, rej) {
        window.onload = res(); 
};    

2.Plain callback: these are callbacks that don't conform to a convention. e.g. setTimeout

var p = new Promise(function(res, rej){
    setTimeout(function() {
        //your business/view logic
        success? res():rej(); //if successful resolve else reject
    }, 2000);
});

In each of the above case the promise (var p) can be wrapped to be returned by a function.

var myAsyncMethod = function () {    
    var p = new ... // as mentioned in 1 or 2
    return p; 
}

Then the usage:

myAsyncMethod()
.then(function(){/* success-handler */})
.catch(function(/* failure-handler */));

Specific to your question you may have many such methods:

function baseAJAXCall (url) {
    new Promise(functoin(rej, res) {
        $.get(url, function(err, data){
            if(err) {
                rej();
            }
            else {
                resolve(data);
            }
        });
    }
};

function callAPIEndpoint(url) {
    return baseAJAXCall(url);
}

function finalPromiseHandler () {
    //your final business/view logic
}

//USAGE
callAPIEndpoint('/my-first-call')
.then(function(data){
    var promiseArray = data.map(function(item){
       return baseAJAXCall(item.url);
    });
    return Promise.all(promiseArray);
})
.then(finalPromiseHandler)
.catch(function(){
    console.log('.error-message.');
});

Ref:

  1. How do I convert an existing callback API to promises?.

  2. http://www.datchley.name/es6-promises/

  3. Links from comments below.

---OLD ANSWER: PLEASE OVERLOOK---

I am familiar with this library : https://github.com/kriskowal/q. And, you can do this using using the q.all and q.allSettled constructs. May be that is what you are looking for.

Normally, the pattern is to create a function that returns a promise.

function someAsyncFuncName1(url) {
    var def = q.defer(); 
    //async function 
    $.get(url, function(err, data){ //suppose
        if(err){
            def.reject();
        }
        else {
            def.resolve(data); //pass the data to the .then() handler. 
        }
    });
    return def.promise; 
}

function someAsyncFuncName2() {
    var def = q.defer(); 
    //async function 
    setTimeout(function(){ //suppose
        //do something
        if(good) {
            def.resolve(); 
        } else {
            def.reject(); 
        }
    }, 1000); //arbitrary timeout of 1 second
    return def.promise; 
}

USAGE:

q.all([someAsyncFuncName1('/api-1'), someAsyncFuncName2()])
.then(function() {
    //final handler
 });

On a similar line of thought one can use q.allSettled() if you want to wait for all promises to return.

Hope this helps.

---EOF OLD ANSWER---

trk
  • 2,106
  • 14
  • 20
  • 1
    Avoid the [deferred antipattern](https://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it) in `someAsyncFuncName1`! – Bergi Aug 30 '17 at 17:46
  • 1
    The `q.defer` is considered as anti pattern [The Deferred anti-pattern](https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns#the-deferred-anti-pattern). If you really need to create a new Promise you should use the regular `new Promise( (resolve, reject) => {})` pattern instead. – t.niese Aug 30 '17 at 17:47
  • Thank you for both your comments. I shall edit the answer to mention this. – trk Aug 31 '17 at 03:48
0

First of all, if async functions used in PromiseA don't return promises, you need to promisify them. You can do that with Promise constructor, but it's much better to use libraries, such as bluebird with their promisify methods.

Let's imagine, that we have two functions getUserIdsAsync and getUserAsync. The first on returns a list of user ids, getUserAsync returns an user data by userId. And you need to get a list of users by their ids. The code of PromiseA could look so:

var promiseA = function() {
  return getUserIdsAsync()
    .then(userIds => {
      let ops = users.map(uid => getUserAsync(uid));
      return Promise.all(ops);
    });
}
alexmac
  • 19,087
  • 7
  • 58
  • 69
  • `.then(userIds => { let userIds = [1, 2, 3];` are you sure? *SyntaxError: redeclaration of formal parameter userIds* – Jaromanda X Aug 30 '17 at 21:21
0

The following snippet shows a solution without using any external library like bluebird. It follows the code snippet in your question (which seems to be more complicate than needed).

You have to collect all api promisses in an array. Then you can call Promise.all() to get a Promise for the end of all api promisses. Then you can do some final stuff, like parsing the result of each promise and continue afterwards.

function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

var apiEndpoint = function (name) {
  return new Promise( (resolve, reject) => {
    setTimeout(() => resolve('API ' + name + ' job done'), 1000);
  });
}

var promiseA = function() {
  return new Promise( (resolve, reject) => {
    const promisses = [];
    for (var i=1; i < getRandomInt(3,6); i++) {
      // 1. Establish objects needed from one API endpoint
      promisses.push(apiEndpoint('This is number ' + i));
    }
    Promise.all(promisses).then( results => {
      // do final stuff
      for (const s of results) {
        // 2. Call API endpoint for each object and parse
        console.log(s);
      }
      // continue ...
      // 3. Only then continue to next promise
      resolve('now it is finished');
    }).catch( err => reject(err) );
  });
}

var finalPromise = function() {
  return new Promise( (resolve, reject) => {
    console.log('finalPromise');
    resolve();
  });
}

promiseA()
  .then( () => finalPromise())
  .catch(err => console.log(err) );

Please hold in mind that this solution is not easy to read. Using external libraries or reducing promisses can improve readability. Maybe you should take a look to the async/await pattern to get a much more better (readable) solution.

Here is a solution with async/await:

function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

const apiEndpoint = function (name) {
  return new Promise( (resolve, reject) => {
    setTimeout(() => resolve('API ' + name + ' job done'), 1000);
  });
}

async function promiseParallel () {
  const promisses = []; 
  for (let i = 1; i < getRandomInt(3,6); i++) {
    promisses.push(apiEndpoint('This is number ' + i));
  }
  for (const p of promisses) {
    const x = await p;
    console.log(x);
  }

  return ('everything is done');
}

promiseParallel().then( result => {
  console.log(result);
}).catch( err => console.log(err) );

If you want call the promisses sequentially you can replace with:

async function promiseSequ () {
  for (let i = 1; i < getRandomInt(3,6); i++) {
    const x = await apiEndpoint('This is number ' + i);
    console.log(x);
  }
  return ('everything is done');
}
Manfred Steiner
  • 1,215
  • 2
  • 13
  • 27