0

I have a problem in my Node.js project. I trying to return the "Report" object after the for loop finished. my code(Updated):

            var apigClient = apigClientFactory.default.newClient({
            accessKey: '*******',
            secretKey: '*******',
            invokeUrl: '*******'
        });

        var pathTemplate = '*******';
        var method = 'POST';
        ///loop - calling API
        for (var i = 0; i < Object.keys(jsonOutput).length; i++)
        {
            var body = {
                        *******: *******,
                        *******: *******
                    };
            apigClient.invokeApi({}, pathTemplate, method, {}, body)
            .then(function (result) {
                Report.numberOfSuccess++;
                console.log(JSON.stringify(result.data));
            }).catch(function (result) {
                Report.numberOfFailed++;
                Report.reportList.push([jsonOutput[i].id,jsonOutput[i].currency]);
            });

        }
        /////finally
        console.log(Report);

my output:

{ reportList: [], numberOfSuccess: 0, numberOfFailed: 0 }
{ reportList: [], numberOfSuccess: 0, numberOfFailed: 0 }
{ reportList: [], numberOfSuccess: 0, numberOfFailed: 0 }
{"status":500,"error":"Internal Server Error"}
{"status":500,"error":"Internal Server Error"}
{"status":500,"error":"Internal Server Error"}
{"status":500,"error":"Internal Server Error"}
{"status":500,"error":"Internal Server Error"}
{"status":500,"error":"Internal Server Error"}
{"status":500,"error":"Internal Server Error"}
{"status":500,"error":"Internal Server Error"}
{"status":500,"error":"Internal Server Error"}

As you can see the the Report object is not ready.

what I need is:

{"status":500,"error":"Internal Server Error"}
{"status":500,"error":"Internal Server Error"}
{"status":500,"error":"Internal Server Error"}
{"status":500,"error":"Internal Server Error"}
{"status":500,"error":"Internal Server Error"}
{"status":500,"error":"Internal Server Error"}
{"status":500,"error":"Internal Server Error"}
{"status":500,"error":"Internal Server Error"}
{"status":500,"error":"Internal Server Error"}
{ reportList: [], numberOfSuccess: 4, numberOfFailed: 0 }
{ reportList: [], numberOfSuccess: 3, numberOfFailed: 0 }
{ reportList: [], numberOfSuccess: 2, numberOfFailed: 0 }

I'v tried to use promise and callback but it isn't works.

DD apps
  • 57
  • 1
  • 6

2 Answers2

1

You need to asyncronously fill up your Report. For example

 const callApi = ( jsonOutput ) => 
    return Promise.map( Object.keys(jsonOutput), ( key ) =>                 
       apigClient.invokeApi({}, pathTemplate, method, {}, body)
       .then( value => {
            Report.numberOfSuccess++;
            Report.reportList ( value );
            return value;
       } )
      .catch( () => Report.numberOfFailed++ )
)

However, your caller-function need to take promises into account. Finally you have have to call the function callApi by

callApi( jsonOutput ).then( report => console.log( "Report: ", report );
sfrehse
  • 1,062
  • 9
  • 22
1

The problem is that you're executing async code inside a for loop which continues to execute in a synchronous fashion. The async calls will complete once the event loop processes its internal FIFO queue of callbacks.

As suggested above, you can use promises to deal with this but the caller function must be able to handle promises.

Another way of doing this if you don't want to deal with promises is to use the async module to process the array of items you're handling. You can look at the eachLimit function which can help you with this.


var asyncFunc = function() {
    return new Promise(function(resolve, reject) {
        resolve('asyncFunc called...');
    });
};

for (var i = 0; i < 10; i++) {
    asyncFunc()
    .then(function(result) {
        console.log('Result from asyncFunc is: ' + result);
    })
    .catch(function(error) {
        console.error(error);
    });

    console.log('Value of i is : ' + i);
}

So based on the above explanation, the above example will output the value of i for every iteration of the for loop before every call to the asyncFunc is processed.


var async = require('async');

var asyncFunc = function() {
    return new Promise(function(resolve, reject) {
        resolve('asyncFunc called...');
    });
};

var mainFunc = function(done) {
    var items = [1, 2, 3, 4, 5, 6, 7, 8, 9];

    async.eachLimit(items, 1, function(item, callback) {
        asyncFunc()
        .then(function(result) {
            console.log('Result for item ' + item + ' is: ' + result);
            callback();
        })
        .catch(function(error) {
            callback(error);
        });
    }, function(error) {
        if (error) {
            console.error(error);

            done(error);
        } else {
            console.log('Done processing items...');

            done();
        }
    });
};

mainFunc(function(error) {
    if (error) {
        console.log(error);
    } else {
        console.log('Processing completed...');
    }
});

Here, using the async library, you can control how each item in your array is processed. Your caller function (i.e. mainFunc), could pass a callback that will be invoked when async.eachLimit completes.

In such case, I suggest you familiarize yourself more with synchronous and asynchronous operations.

Update

Here is a simple example using only promises.

var asyncFunc = function(item) {
    return new Promise(function(resolve, reject) {
        console.log(item);
        resolve('asyncFunc called...');
    });
};

var mainFunc = function() {
    var items = [1, 2, 3, 4, 5, 6, 7, 8, 9];

    var promises = items.map(asyncFunc);

    return Promise.all(promises);
};

mainFunc()
.then(function(result) {
    console.log(result);
})
.catch(function(error) {
    console.error(error);
});

The Promise.all() method returns a single Promise that resolves when all of the promises in the iterable argument have resolved or when the iterable argument contains no promises. It rejects with the reason of the first promise that rejects.

M3talM0nk3y
  • 1,382
  • 14
  • 22
  • Having callback functions and promises within the same function is not a good style. I definitely suggest one just one form. In this case probably promises combined with $q or Bluebird is fine. – sfrehse Aug 13 '17 at 08:24
  • Thanks Maximo. can you please write the promise format (1) according to my code(because I little confuse where to put what). – DD apps Aug 13 '17 at 09:03
  • @DorDana Updated my answer with an example using only promises. Hope this helps you out. Please accept this answer if it helped you solve your problem. – M3talM0nk3y Aug 13 '17 at 12:08
  • @sfrehse "Having callback functions and promises within the same function is not a good style." Do you have a reference for this or is this your opinion? Most, if not all, of Node's Core libraries are written using callbacks. Also, Node.js added native promises in stable `version 0.12` so unless there is a benefit to using $q or Bluebird over native promises, why would I introduce another library? – M3talM0nk3y Aug 13 '17 at 12:12
  • @Maximo You are completely right, this is not a general recommendation for the entire code base since it is not easy possible (not impossible due to https://stackoverflow.com/questions/22519784/how-do-i-convert-an-existing-callback-api-to-promises) . However, if it is possible code becomes more maintainable if you chose only one mechanism. And of course, $q and Bluebird are just techniques to simplify things. – sfrehse Aug 13 '17 at 15:55