Search code examples
javascriptes6-promise

How can I make a recursive Promise call to be the return for a reduce in Javascript?


I'm trying to create a recursive Promise call inside a reduce in JS. The goal for my system here is to make n big calls for each item in the array that reduce plays with, then, if, inside that reduce that big call decides it needs n smaller calls before it gets back to the big ones, then the reduce shouldn't skip to the next item, just wait for it to finish.

My current code is:

function download_preliminary_files_1() {
    let demo_schema_download = new Base_Ajax_Requester(
        localized_data.ajax_url,
        {
            'ajax_action': 'download_demo_file_schema',
            'ajax_nonce': localized_data.ajax_nonce,
            'backend': {
                'data_handle': 'download_demo_file_schema_data',
                'data':
                    {
                        'demo_handle' : 'demo-2',
                    }
            }
        }
    );

    let import_files_download = demo_schema_download.call().then(function() {
        fake_data.steps_to_import.reduce(function(previous_promise, next_step_identifier) {
            return previous_promise.then(function() {
                let file_download = download_demo_file({
                    'demo_handle' : 'demo-2',
                    'step' : next_step_identifier
                }).call();

                file_download.then(function(response) {
                    /**
                     * Here, if I detect that response.remaining_number_of_files > 0, I should start
                     * a chain that keeps calling `download_demo_file` with new parameters.
                     *
                     * Then, when this chain is done, resume the normal reduce behavior.
                     */
                });
            });
        }, Promise.resolve())
    }).catch(function(error) {
        console.log( 'Got this error:' + error);
    });

    return import_files_download;
}

Where Base_Ajax_Requester is a helper class that handles AJAX requests and returns a Promise when it's done so code could be written around it.

My fake_data is:

let fake_data = {
    'demo_handle' : 'demo-2',
    'steps_to_import' : [ 'elementor-hf','post', 'nav_menu' ]
}

As you can see fake_data.steps_to_import.reduce(.. will go through these 3 values, for each of them call download_demo_file, wait for it to finish, then proceed to the next. We could say that I'd like to, between elementor-hf and post to have n smaller calls.

The initial call to download_demo_file is seen up there, here's what will always come back from the back-end:

{
    'message' : '...',
    'file_number' : 1, //Which -n.xml file has been downloaded where n is this number.
    'remaining_number_of_files' : 1 //Calculates, based on what file was downloaded how many files are left. The system knows internally how many files it has to download.
}

The retry call, again, with download_demo_file would look like:

{
    'demo_handle' : 'demo-2',
    'step' : next_step_identifier,
    'file_counter' : 2 //Dynamic. This will signal that it needs to now download file-2.xml.
}

...and so on, until the back-end sends remaining_number_of_files : 0, then it all stops because there are no more files to download and it can skip to the next big call.

How can I achieve this?


Solution

  • Inside each reduce callback, I'd make a function that calls download_demo_file (with a changing file_counter), and after that Promise resolves, recursively returns a call of itself if remaining_number_of_files > 0. This will mean that getProm() will continually call itself until the remaining_number_of_files > 0 condition is not fulfilled anymore, and that only after that will the whole Promise for that particular reduce iteration resolve.

    let import_files_download = demo_schema_download.call().then(function() {
      fake_data.steps_to_import.reduce(function(previous_promise, step) {
        let file_counter = 1;
        return previous_promise.then(function() {
          const getProm = () => download_demo_file({
            demo_handle: 'demo-2',
            step,
            file_counter
          }).call()
            .then((response) => {
              file_counter++;
              return response.remaining_number_of_files > 0
                ? getProm()
                : response
            });
          return getProm();
        });
      }, Promise.resolve())
    }).catch(function(error) {
      console.log('Got this error:' + error);
    });
    

    The code would probably be a lot easier to read and understand with async/await, though:

    await demo_schema_download.call();
    const import_files_download = [];
    for (const step of fake_data.steps_to_import) {
      let response;
      let file_counter = 1;
      do {
        response = await download_demo_file({
          demo_handle: 'demo-2',
          step,
          file_counter
        }).call();
        file_counter++;
      } while (response.remaining_number_of_files > 0);
      import_files_download.push(response); // is this needed?
    }
    return import_files_download; // is this needed?
    

    Catch in the consumer of the async function.