简体   繁体   中英

Javascript promise in synchronous loop

I have fairly simple task but it makes me pull my hair out. Already searched whole Internet and none of the solutions found can be directly translated to my problem. It's a follow up question to JavaScript - no return

Here's what I got:

var worksheetArray;
var filtersArray =[];

function testu(){

  filtersArrayFetch();
  console.log("finished fetching");
  console.log(filtersArray);
  //do more stuff with array

}

function filtersArrayFetch()
{

    workbook = viz.getWorkbook();
    sheet=viz.getWorkbook().getActiveSheet();
    worksheetArray = sheet.getWorksheets();


        for (var i=0; i<worksheetArray.length; i++) {
          (function(num){
            worksheetArray[i].getFiltersAsync()
                .then(function(promise){
                    for (j=0;j<promise.length;j++)
                    {
                        filtersArray.push(promise[j].getFieldName());
                    }
               })

          })(i);  

         }
console.log("after for");

}

In plain English -I have an array of worksheets fetched with synchronous Tableau API function. getFiltersAsync() returns Promise as an Array. On this Array I would like to perform getFieldName and push it to final Array I will be using later in the code. Code works until worksheetArray[i].getFiltersAsync() but does not evaluate .then() and returns undefined to testu() function. testu() is being called by button click. I need to keep IE(Edge) compatibility so Promise.all() is not an option.

What is wrong with the script? Why it's not evaluating .then() ?

EDIT:

I've managed to solve the problem using self-invoking runner function:

function filtersearch2(i){
    workbook = viz.getWorkbook();
    sheet=viz.getWorkbook().getActiveSheet();
    worksheetArray = sheet.getWorksheets();

    var filtersArray=[];
    var d=$.Deferred();

    (function runner(i){ 

        worksheetArray[i].getFiltersAsync().then(
        function(filtersArrayReturnedInPromise){

        for (z=0;z<filtersArrayReturnedInPromise.length;z++){
            field = filtersArrayReturnedInPromise[z].getFieldName();

            if (field.search("AutoFilter")>0 && field.search("Action")==-1 ){
                filtersArray[filtersArray.length]=field; 
            }

        }

        if (i==worksheetArray.length-1){
            var uniq = filtersArray.reduce(function(a,b){
            if (a.indexOf(b) < 0 ) a.push(b);
            return a;
                },[]);

        console.log("resolving filtersearch");
        d.resolve(uniq);

        } else {

            i++;
            runner(i);
        }

        });

    })(i)

return d.promise();
}

please use/declare (local) variables and stop polluting the global namespace.

I've rewritten your code to properly deal with the promises.

filtersArrayFetch() has to return a promise so that other functions can deal with the result of its computation. And testu() needs to use this promise in order to determine when filtersArrayFetch is done.

Your code didn't wait for the promises to resolve, before trying to process/log the filtersArray . And your approach with the global/shared filtersArray that is modified in the promises is pretty dangerous/unpredictable itself. Take that pattern to a function that you call twice and all hell will break loose; you'll not only rip out your hair, it'll set your head on fire when trying to reproduce some result or debugging the mess that this can create.
Because most of your code will have no clue when some random promise will finish and start adding items to that array; nor what function call these items are coming from.

function testu(){
    filtersArrayFetch().then(filtersArray => {
        console.log("finished fetching");
        console.log(filtersArray);      
        //do more stuff with array
    });
}

function filtersArrayFetch() {
    //a few functions that define some tasks/steps that need to be done 
    //in order to do the whole task.

    //Array<Array<*>> -> Array<*>
    var flatten = arrays => [].concat(...arrays);

    //filter -> fieldName
    var getFieldName = filter => filter.getFieldName();

    //Array<filter> -> Array<fieldName>
    var filtersToFieldNames = filters => filters.map(getFieldName);

    //worksheet -> Promise<Array<fieldName>>
    var worksheetToFieldNames = worksheet => worksheet.getFiltersAsync().then(filtersToFieldNames);

    //telling by your code, to this point everything should be sync.
    var worksheets = viz.getWorkbook().getActiveSheet().getWorksheets();

    return Promise.all(worksheets.map(worksheetToFieldNames)).then(flatten);
}

worksheets.map(worksheetToFieldNames) transforms an Array<worksheet> into an Array<Promise<Array<fieldName>>> . After running this through Promise.all() we have a Promise<Array<Array<fieldName>>> . The promise of an Array of Arrays; for each worksheet an array of fieldnames.

And after running this through .then(flatten) we have a plain Promise<Array<fieldName>> .

But from the point we started to deal with promises we will have to continue dealing with promises. We cannot unwrap the values and get sync anymore. So the very only thing this function can return is a Promise (of whatever) .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM