bene96 bene96 - 1 month ago 17
Node.js Question

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 retireve method to be passed into a check status function.

However the poblem 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
    checkRetreiveStatus

  3. The naive approach would be to pass the parameters from the original
    checkRetreiveStatus
    call into a new instance of
    checkRetreiveStatus
    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?

Answer

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);
});