Home > other >  In JavaScript, how to get back the results of a callback function for all the elements of an array,
In JavaScript, how to get back the results of a callback function for all the elements of an array,

Time:09-28

I have a function that calls an API, and the API accepts a callback:

const callApi = async (param1, param2) => {
    api.doSomething(param1, param2, (result) => {
        // I want to return the result as the result of callApi
    }
}

And I have a list/array of objects on which I want to call the Api function, treat the results one by one, and then sort them and pick the one that fits my criteria.

I do NOT want to shove all the logic in the callback. I know I could, but it would result in ugly looking code that will be hard to read 1 year from now.

Something like:

let results = myArrayOfObjects.map(function(myObject){
    callApi(myObject.field1, myObject.field2)
        .then(
            // here I'd like to get the result of api.doSomething
            // do more stuff here with that result
        )

    // return an object here, or a promise

})

// do something with the results after all objects in myArrayOfObjects have been processed
// for example, sort()

If the results are promises, I am thinking of using Promise.all to wait for all of them to complete. Otherwise, it would be even easier to work with the results.

Thank you.

CodePudding user response:

You could first promisify your API so that it returns a Promise that resolves to your result provided by your callback. It might be worth checking your API implementation and seeing if it already returns a Promise (if that's the case, then you can just return that Promise and ditch the callback)

const callApi = (param1, param2) => {
  return new Promise(resolve => { 
    api.doSomething(param1, param2, (result) => {
        resolve(result);
    });
  });
}

Once you have callApi returning a Promise, you can map over your array and call callApi() to fire your API request, as well as to return the Promise. At this point you can extend your promise chain by attached a .then() to it and return a transformed version of your Promise:

const mapped = myArrayOfObjects.map(myObject => {
    return callApi(myObject.field1, myObject.field2)
        .then(result => // result of api.doSomething
            // do more stuff here with that result
            return result2; // return the transformed result (ie: `result2`)
        );
});

The above piece of code can be re-written a little more nicely using async/await isntead:

const mapped = myArrayOfObjects.map(async myObject => {
    const result = await callApi(myObject.field1, myObject.field2);
    // do more stuff here with that result
    return result2; // result2 represents the "mapped"/transformed version of `result`
});

Once you have an array of promises mapping to your desired value, you can use Promise.all() to wait for all Promises to resolve and retrieve your result:

Promise.all(mapped).then(results => {
    // results is an array based on the mapped values
});

Simple Example (using setTimeout to simulate an asynchronous API request):

CodePudding user response:

Instead of using .then(), create a function that does all of the stuff you want it to do and then send that function into callApi as a parameter.

const doStuff = (results) => {
    // Do stuff here
}

callApi(myObject.field1, myObject.field2, doStuff)

And then in the callApi function:

const callApi = async (param1, param2, stuffDoerFunc) => {
    api.doSomething(param1, param2, stuffDoerFunc);
}

Now that I think about it, you could probably even simplify it further:

const doStuff = (results) => {
    // Do stuff here
}

api.doSomething(myObject.field1, myObject.field2, doStuff);

CodePudding user response:

// Wrap your object/array processor in a standalone function
function processObjects() {
    let results = myArrayOfObjects.map(function(myObject){
        callApi(myObject.field1, myObject.field2)
            .then(
                // here I'd like to get the result of api.doSomething
                // do more stuff here with that result
            )    
    
        // return an object here, or a promise
    
    })
}

// Wrap your post-processing in a second function
function processResults() {
    // do something with the results after all objects in myArrayOfObjects have been processed
    // for example, sort()
}

// Create a master function to call, which calls both functions in order, 
// and does not let the second one start until the first one completes
async function processObjectsThenResults() {
    await processObjects();
    await processResults();
}

All this wizardry is unnecessary in a sequential functional language, but in the synchronous swamp of JS you need to force it to wait until your first group of commands finishes before starting the post-processing, otherwise it will try to just do it all at once and overlap and mayhem will ensue!

To modify this approach to pass results, you could just push them from one function to the other. A hacky way to do this would be passing the result of the first function into the second, like this:

async function processObjectsThenResults() {
    someArrayVariable = await processObjects();
    await processResults(someArrayVariable);
}

You would need to adjust the second function to be able to receive and interpret the params in the format that the first function outputs them to.

  • Related