bene96
bene96

Reputation: 65

How to recursively call an asynchronous function?

I've defined this function:

// retrieves zip of package manifest supplied

var retrieveZip = function(metadataClient, args, callback) {
  metadataClient.retrieve(args, function(err, result) {
    metadataClient.checkRetrieveStatus({id: result.result.id, includeZip: 'true'}, function(err, result) {
      if(result.result.done) {
        return callback(result);
      }
      // else check again!(how??)
    });
  });
}

retrieveZip(metadataClient, args, function(result) {
      console.log(result);
});

The idea is that the client will attempt to retrieve a zip file based on the metadata supplied in args. The API documentation (Salesforce SOAP API) requires the id supplied in the result of the retrieve method to be passed into a check status function.

However the problem is this:

  1. On the first check, if the result is 'done' then just return to the callback the result object from checkRetreiveStatus (contains the result)

  2. BUT if the result isn't done, I need to call checkRetrieveStatus again... from inside checkRetrieveStatus

  3. The naive approach would be to pass the parameters from the original checkRetrieveStatus call into a new instance of checkRetrieveStatus but obviously it's impossible to know how many times this would be invoked.

It sounds like I need a recursive solution to this? Would using a while-loop would introduce problems with asynchronous calls?

Upvotes: 1

Views: 1543

Answers (2)

JLRishe
JLRishe

Reputation: 101662

From your description, it sounds like you just want to call checkRetrieveStatus until it's done, not retrieve and checkRetrieveStatus. Is that correct?

If so, the thing to do is to extract the status check out into your own function that can recursively call itself, like this:

var checkStatus = function(metadataClient, id, callback) {
    metadataClient.checkRetrieveStatus({ id: id, includeZip: 'true' }, function(err, result) {
        if (result.result.done) {
            callback(result); 
        } else {
            checkStatus(metadataClient, id, callback);
        }
    });
};

var retrieveZip = function(metadataClient, args, callback) {
  metadataClient.retrieve(args, function(err, result) {
      checkStatus(metadataClient, result.result.id, callback);
  });
}

retrieveZip(metadataClient, args, function(result) {
      console.log(result);
});

And if you're worried about hogging system resources while it repeatedly polls the result, you can include a delay between checks:

var checkStatus = function(metadataClient, id, callback) {
    metadataClient.checkRetrieveStatus({ id: id, includeZip: 'true' }, function(err, result) {
        if (result.result.done) {
            callback(result); 
        } else {
            setTimeout(function () {
                checkStatus(metadataClient, id, callback);
            }, 100);
        }
    });
};

var retrieveZip = function(metadataClient, args, callback) {
  metadataClient.retrieve(args, function(err, result) {
      checkStatus(metadataClient, result.result.id, callback);
  });
}

retrieveZip(metadataClient, args, function(result) {
    console.log(result);
});

Upvotes: 2

O_Z
O_Z

Reputation: 1563

Better to split the function so you do not have to re-enter the top.something like this (did not try to run this, so just get the idea)

// retrieves zip of package manifest supplied

var retrieveZip = function(metadataClient, args, callback) {
  metadataClient.retrieve(args, function(err, result) {
    doCheck(result,function(res){
       callback(res)
    })

  });
}


function doCheck(result,cb){
 metadataClient.checkRetrieveStatus({id: result.id, includeZip: 'true'}, function(err, innerResult) {
     if(result.result.done) {
            return cb(result);
      }else{
    doCheck( innerResult ,cb)
    }
  });
}

retrieveZip(metadataClient, args, function(result) {
      console.log(result);
});

Upvotes: 0

Related Questions