Function with defer that calls itself repeatedly

I am attempting to utilize a recursive function where each function runs only after the previous one has completed. This is the code I have written:

var service = ['users', 'news'],
    lastSync = {
                 'users' : false,
                 'news' : false
               };
db.transaction(function (tx) {
   lastSyncFunc(tx,service,0).then(function(){
       console.log(lastSync);
   });
});

function lastSyncFunc(tx,service,index){
   deferred = $q.defer();
   tx.executeSql("SELECT time FROM last_sync WHERE fService = ? ORDER BY id DESC LIMIT 1", [service[index]], function (tx, result) {
       if (result.rows.length > 0) {
           lastSync[service[index]] = result.rows.item(0).fTime;
       }
       return ++index<service.length ? lastSyncFunc(tx,service,index) : deferred.resolve();
   });
   return deferred.promise;
}

Currently, my program is returning false for both lastSync.users and lastSync.users because this section is being executed before the function completes.

Answer №1

It may not always be the best decision to handle manual multiple asynchronous calls.
Consider using $q.all() for a more efficient approach.

Simplify the process by first creating a promisified version for a single query:

const pDbExec = (tx, sql, params = []) => {
  let deferred = $q.defer();
  tx.executeSql(sql, params, (tx, res) => deferred.resolve(res));
  return deferred.promise();
}

The initial step should involve checking if there is already a promisified version available for the library/methods being used.

Then, proceed to call $q.all while mapping your service list into promises:

const SQL_SvcLastSync = `SELECT time FROM ... LIMIT 1`;
db.transaction(tx => {
  $q.all(service.map(svc => pDbExec(tx, SQL_SvcLastSync, [svc])))
    .then(results => 
       results.map(res => 
         res.rows.length > 0 ? res.rows.item(0).fTime : null))
    .then(results => console.log(results));
});

To format results as key/value pairs, you have two options:

  • Add a reducer:
    .then(results => 
      results.reduce((acc, res, i) => (acc[service[i]]=res, acc), {}))
  • Provide key/value pairs (where values are promises) instead of an array map to $q.all, so they will be resolved under the same keys.
    Note that you may need to modify the intermediate mapper in this case.

A simple solution involves adding a parameter to save the same deferred object between recursive calls:

You can try something like this:

function lastSyncFunc(tx, service, index, def){
   var deferred = def || $q.defer();
   tx.executeSql(
     "SELECT time FROM last_sync WHERE fService = ? ORDER BY id DESC LIMIT 1", 
     [service[index]], 
     function (tx, result) {
       if (result.rows.length > 0) {
           lastSync[service[index]] = result.rows.item(0).fTime;
       }
       return ++index<service.length ? 
         lastSyncFunc(tx, service, index, deferred) : 
         deferred.resolve();
   });
   return deferred.promise;
}

This method provides a deferred object to the maximum depth, allowing it to be resolved appropriately.

Answer №2

If you want to accomplish this task efficiently, it is recommended to steer clear of recursion as explained in detail here under "The Collection Kerfuffle" section.

The suggested approach involves organizing everything within a db.transaction(function() {...}) framework, but the code becomes more comprehensible if you extract tx.executeSql(...) and convert it into a separate function with promises.

After implementing these changes, your code will resemble something along these lines:

var service = ['users', 'news'],
    lastSync = {
        'users' : false,
        'news' : false
    };
db.transaction(function (tx) {
    return service.reduce(function(promise, serviceItem) {
        return promise.then(function() {
            return executeSqlPromisified(tx, serviceItem).then(function(fTime) {
                if(fTime !== null) {
                    lastSync[serviceItem] = fTime;
                }
            });
        });
    }, $q.when(null)).then(function() {
        console.log(lastSync);
    });
});

function executeSqlPromisified(tx, serviceItem) {
    var deferred = $q.defer();
    tx.executeSql("SELECT time FROM last_sync WHERE fService = ? ORDER BY id DESC LIMIT 1", [serviceItem], function (tx, result) {
        if (result.rows.length) {
            deferred.resolve(result.rows.item(0).fTime);
        } else {
            deferred.resolve(null);
        }
    });
    return deferred.promise;
}

Initially, this format may seem overwhelming, but once you familiarize yourself with this pattern, navigating through the code will become easier.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

In the present technological landscape, is it still considered beneficial to place Javascript at the bottom of web pages?

As a beginner in web programming, I've recently delved into Javascript. A hot debate caught my attention - where should javascript be placed, at the top or at the bottom of a webpage? Supporters of placing it at the top argue that a slow loading time ...

Validating email addresses using jQuery regular expressions

Probable Match: How can I validate email addresses using a regular expression? I have explored various options, but none have proven effective for validating email: [email protected] This is the client-side function I have implemented for a cust ...

Webpack-dev-middleware is serving the bundle on a port that is distinct from the application

Recently, I've been developing a React boilerplate that fully utilizes Apollo-Client and GraphQL. The setup of my application consists of one node process overseeing an Express server on port 3000 to render the app, and another Express server on port ...

Can anyone recommend a drag-and-drop feature in Javascript that functions seamlessly on both iPad and PC devices?

Can anyone recommend a JavaScript plugin that allows for drag and drop functionality on both touch and mouse enabled devices, including IOS, Android, PC, and Mac? For touch-enabled devices, I found an example here: And for mouse-enabled devices, there is ...

Uninterrupted jQuery AJAX calls

I have a scenario where I need to periodically update an image on my view using ajax every 10 seconds. Any suggestions on how I can achieve this? Appreciate the help. ...

Adjust the position of the content to the bottom for a form-group in Bootstrap v4

Currently, I am facing an issue where my label is overflowing and wrapping around the input field, causing it not to align at the bottom of the div. Is there a straightforward CSS solution for this problem? I have experimented with position relative/absol ...

What is the best way to eliminate all click event handlers with jQuery?

I'm encountering an issue where multiple click events are being attached to a button. Specifically, when a user clicks on an 'Edit' link, the following JQuery code is executed: $("#saveBtn").click(function () { saveQuestion(id); }); Ea ...

Setting up a Variable with an Object Attribute in Angular

I am attempting to create a variable that will set a specific property of an object retrieved through the get method. While using console.log in the subscribe function, I am able to retrieve the entire array value. However, as a beginner, I am struggling ...

Change the background color according to the user's input text

I want to create a text box where users can input color keywords like blue, lime, or black. When they click submit, I want the background color of the page to change accordingly. This is what I have so far: <label for="color">Color: </label> ...

The hover functionality fails to activate when a z-index is applied

My issue revolves around 2 divs: one containing a link and the other a box-shaped container. The link is set with position:fixed; causing it to fly over the container div. To resolve this, I attempted to assign a negative z-index value to the link, but unf ...

How to splice or add elements to an array using JavaScript function

Two buttons on my webpage are designed to add arrays to the orderArray. The arrays are then displayed as an HTML table, with each array having a corresponding button to remove it from the table. The removal process is working as intended, but after using . ...

Increasing the size of elements with jQuery animate method

I've been utilizing the animate function in jQuery to dynamically resize a content area upon hovering over the element. Although the script is functioning correctly, I'm facing a challenge in preventing the script from resizing the content multi ...

Tips for creating dynamic alerts using mui v5 Snackbar

My goal is to call an API and perform several actions. After each action, I want to display the response in a Snackbar or alert. Despite iterating through the messages in a map, I'm only able to show the first response and none of the others. Here is ...

Issue encountered in Angular 2 Heroes Tour, Step 7

I checked multiple forums, but didn't come across any relevant information. After starting the tutorial just 3 days ago and reaching Step 7, I encountered an error when incorporating the InMemoryWebApiModule as instructed in the steps. The error can ...

What distinguishes defining a function through a prototype versus as a class property?

Check out this code snippet: Apple defines its function using a prototype. Banana defines its function using a class property. var Apple = function(){} Apple.prototype.say = function(){ console.debug('HelloWorld'); } var Banana = functio ...

Automatically pre-fill and send hidden form

I'm working on a form and have set up a handler for the submit button like this: $( '#submit_btn' ).click( function( data ){ theForm = document.getElementById( 'realForm' ); theForm.meetingName.value = document.getElement ...

having difficulty accessing the value within the Angular constructor

My current issue arises when I click on a button and set a value within the button click method. Despite declaring the variable in the constructor, I am unable to retrieve that value. The code snippet below demonstrates this problem as I keep getting &apos ...

Steps to initiate a slideUp animation on a div using jQuery

Is there a way to make a div slide up from the bottom of the page to cover 30% of the screen when a user clicks a button, and then slide back down when the button is clicked again? Any suggestions on how I can achieve this? Thank you! EDIT 1) $(document ...

Prevent Fixed Gridview Header from being affected by browser Scroll-bar using JQuery

Is there a way to make the fixed header responsive to only one scroll bar in JQuery? Specifically, is it possible to have it respond solely to the div's scroll bar and not the browser's scroll bar? I attempted to remove the browser's scroll ...

Ways to Access a Variable from One Controller to Another Controller without Utilizing Scope

Just starting out with angularjs, I have an issue that needs quick solutions. How do I pass a variable from one controller to another without using scope or Service? ...