Using results accumulated after triggering a query multiple times

Using the example here:
https://docs.retool.com/docs/scripting-retool#triggering-a-query-for-each-item-in-an-array

var rows = [{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }];
var results = []; // My results are accumulated here

function runQuery (i) {
  if (i >= rows.length) {
    console.log('Finished running all queries');
    return;
  }
  var data = rows[i];
  console.log('Running query for row', data);

  query1.trigger({
    additionalScope: {
      data: data
    },
    // You can use the argument to get the data with the onSuccess function
    onSuccess: function(data) {
      results.push(data) // accumulating results here
      runQuery(i + 1);
    }
  });
}

runQuery(0);

I want to accumulate the results returned from each query I triggered and populate a table widget with those results.

How do I accomplish that?

I tried to refer to the results from myquery by using this in the Data input field {{myquery.data}} of my table widget but it just keeps saying: Value given is null

myquery

const report_ids = ["EXT997", "EXT869"]

const promises = report_ids.map(id => {
    return analytic_date_range.trigger({
        additionalScope: {
            report_id: id
        }
    });
});

return Promise.all(promises);

I can see I do get valid results returned when I actually run myquery though.

Hey there @pjc714, and welcome to the community! Thanks for the question, and the detailed explanation. I’m guessing that the problem here is triggering - Run JS Code queries need to be triggered manually. Could you try adding a button under your table that triggers the query and see if that works?

Thanks for your reply Justin, I did as you suggested and triggered my JS Code Query via a DatePicker (of course triggering via Button works as well) and I was able to populate a Table widget by referring to the results returned by my query in the Table widget Data field as {{get_reports_for_date.data}}

This is the get_reports_for_date query I used in case anyone else is trying to do something similar:

   // Get the list of report ids
   const report_ids = properties.data.Items[0].MapAttribute.report.map((report) => {return report.report})

   // Run my analytic_date_range query for a different report id each time
    const promises = report_ids.map(id => {
        return analytic_date_range.trigger({
            additionalScope: {
                report_id: id
            }
        });
    });

    // Process the results of all the queries executed and combine them into an array to be used in a Table widget
    return Promise.all(promises).then(data => {
      let reports = [] // The combined results
      
      // Reformat the data as required and 'combine' push the result into the reports array
      data.map((result) => { 
        result.Items.map((item) =>  {
          reports.push({report_id: item.object_id, timestamp: item.timestamp})
        })
      })
      return reports; // This data will be presented in my Table widget
    });

The above asynchronous example above worked fine for me but just out of curiosity I still do not understand how you would return data from the synchronous example documented in your code here, can you please explain how to achieve that too.

Triggering a query for each item in an array
https://docs.retool.com/docs/scripting-retool#triggering-a-query-for-each-item-in-an-array

For example doing this:

I modified runQuery() to return a value on completion like so:

if (i >= rows.length) {
   console.log('Finished running all queries');
   return "Query Finished"; // Return a value on completion
}

and then I tried doing this but it seems like runQuery(0) is running asynchronously so I don't get the value being returned because runQuery(0) had not completed when the value was returned

return runQuery(0) ;

Glad to hear you got it working @pjc714! I’m not sure what you mean by “how you would return data” - the sync example in the documentation can return data, it’s just a question of how you trigger it.

Hi Justin, in the Promise.all version I am returning the Promise.all as result in the other example in your documentation in the code below please show how I would return a result and use it.

var rows = [{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }];

function runQuery (i) {
  if (i >= rows.length) {
    console.log('Finished running all queries');
    return "Hello World";  // Do I return something here?
  }
  var data = rows[i];
  console.log('Running query for row', data);

  query1.trigger({
    additionalScope: {
      data: data
    },
    // You can use the argument to get the data with the onSuccess function
    onSuccess: function(data) {
      runQuery(i + 1);
    }
  });
}

runQuery(0);  

If I use the code above and return a value where I said Do I return something here?
and do this

console.log("BEFORE")
let myresult = runQuery(0);
console.log(=======> myresult: , myresult)
console.log("AFTER")

myresult is undefined because in the screenshot below its apparent that runQuery() is being run asynchronously because you can see query1 being executed after the AFTER message is logged. So I am not understanding how I can actually use myresult in that case. Can you please show example of just printing out the value of myresult by running the query manually

Retool2

Without using a Promise or temp state, I don't know of another way to return an aggregated result off the top of my head. The simple/synchronous example here would only be useful for looping through queries where you don't need an aggregated return, I.E. some forms of bulk writes or some other outgoing requests which aren't displayed to the user directly.

To see the results logged sequentially, we'd need to do modify it to something like this:

var rows = [{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }];

function runQuery (i) {
  if (i >= rows.length) {
    console.log('Finished running all queries');
    return "Hello World";  
  }
  var data = rows[i];
  console.log('BEFORE RUN #'+i);

  query1.trigger({
    additionalScope: {
      data: data
    },
    // You can use the argument to get the data with the onSuccess function
    onSuccess: function(data) {
      let myResult = data
      console.log(myResult)
      console.log("AFTER RUN #"+i)
      runQuery(i + 1);
    }
  });
}

runQuery(0);  

Which gives us this console output:

If you need to return aggregated data for a loop of .trigger()'s of unknown length (I.E. the query is only known to be complete based the return of one of the individual queries) then you'll probably need to use a temporary state with a default value of [] and .setIn(index, data) to save results as they are returned inside the onSuccess function of the loop. There's an example of doing this with the airtable api here: Keep running REST API requests until all results are returned (Load all rows from an Airtable API)

Thanks Alex, got it :smiley: :+1: