Search code examples
javascriptsqlitecordovapromiseweb-sql

Problematic promises in phonegap code, using webSQL (SQLite)


I'm struggling with an error with Phonegap/Cordova. I'm building and testing my app within a connected device, and this is the error I get:

INVALID_STATE_ERR: DOM Exception 11

None of the solutions I've found works. Most of them are aimed at using ajax, which is not the case here. I found another one regarding phonegap, in which they say it might be because the device is not ready, but I've checked the device is ready in my case.

This is the code in which the error is thrown (I'm using a Promise polyfill BTW, since it's not supported in Cordova so far):

/**
 * Clients insertion
 * @param tx
 * @param clientes
 * @return {Promise}
 */
clientes = function(clientes) {

    return new Promise(function(resolve, reject) {
        var clientesCount = clientes.length;

        if (clientesCont === 0){
            resolve();
        }
        else {
            APP.db.transaction(function(tx) {
                $.each(clientes, function(i, cliente) {

                    var idclienteLocal;

                    // Current client
                    tx.executeSql("INSERT OR REPLACE INTO clientes (id, name, ...) " +
                        "VALUES " +
                        "(?,?,...)",
                        [cliente.id, cliente.name],
                        function(tx, res) {

                            clientesCount--;

                            idclienteLocal = res.insertId;

                            // Clien phones
                            telefonosCliente(tx, cliente, idclienteLocal).then(function() {
                                // Client credits
                                creditosCliente(tx, cliente, idclienteLocal).then(function() {
                                    if (clientesCount === 0) {
                                        resolve();
                                    }
                                }).catch(function(error) {
                                    reject('Error créditos cliente ' + cliente.empresa + ': ' + error);
                                });
                            }).catch(function(error) {
                                reject('Error teléfonos cliente ' + cliente.empresa + ': ' + error);
                            });

                        }, function(tx, error) {
                            reject(error.message);
                        });

                });
            });
        }
    });
}

I suspect it might have something to do with the loop (I'm using jQuery for this), so I tried queueing each iteration when the previous one has been completely resolved, by using a recursive function, but it doesn't even work in the browser (the Promise in the first procesarCliente() call, within clientes() won't be resolved). Here's the modified code:

/**
 * Recursive function to process each client one after the other
 * @param tx
 * @param clientes
 * @param cliente
 * @return {Promise}
 */
procesarCliente = function(tx, clientes, cliente) {
    return new Promise(function(resolve, reject) {
        // Current client
        tx.executeSql("INSERT OR REPLACE INTO clientes (id, name, ...) " +
            "VALUES " +
            "(?,?,...)",
            [cliente.id, cliente.name,...],
            function(tx, res) {
                var idclienteLocal = res.insertId;

                // Client phones
                telefonosCliente(tx, cliente, idclienteLocal).then(function() {
                    // Client credits
                    creditosCliente(tx, cliente, idclienteLocal).then(function() {                         
                        if (clientes.length === 0) {
                            resolve();
                        }
                        else {
                            procesarCliente(tx, clientes, clientes.shift());
                        }
                    }).catch(function(error) {
                        reject('Error créditos cliente ' + cliente.empresa + ': ' + error);
                    });
                }).catch(function(error) {
                    reject('Error teléfonos cliente ' + cliente.empresa + ': ' + error);
                });

            }, function(tx, error) {
                reject(error.message);
            });
    });
},
/**
 * Clients insertion 
 * @param tx
 * @param clientes
 * @return {Promise}
 */
clientes = function(clientes) {

    return new Promise(function(resolve, reject) {
        var cliente,
            clientesCont = clientes.length;

        if (clientesCont === 0){
            resolve();
        }
        else {
            APP.db.transaction(function(tx) {
                cliente = clientes.shift();
                procesarCliente(tx, clientes, cliente).then(function() {
                    resolve();
                }).catch(function(error) {
                    reject(error);
                });
            });
        }
    });
}

Any help on fixing the second approach, and most important, how to get it working in phonegap?

EDIT

I've added a verification code to inspect each of the tables in the local DB, and surprisingly, it apparenntly creates only two tables called "undefined" and "item" (I didn't even use such a table).

The verification code here:

    if (APP.DEBUG) { // this is true
        // Verify inserted data when triggered event 'app.load.all' just after the very last item in the DB has been inserted
        $wrapper.on('app.load.all', function() {
            APP.db.transaction(function(tx) {
                console.log('Verifying DB');
                tx.executeSql("SELECT name FROM sqlite_master WHERE type='table'", [],
                    function(tx, res) {
                        if (res.rows.length) {
                            $.each(res.rows, function(i, tabla) {
                                if (tabla.name !== '__WebKitDatabaseInfoTable__') {
                                    console.log('Verifying table ' + tabla.name);
                                    tx.executeSql("SELECT * FROM " + tabla.name, [],
                                        function(tx, res) {
                                            console.log("Table " + tabla.name + " has " + res.rows.length + " records");

                                        },
                                        function(tx, error) {
                                            console.log('Error verifying table ' + tabla.name + ':' + error.message);
                                        });
                                }
                            });
                        }
                    }, function(tx, error) {
                        console.log('Error verifying DB tables: ' + error.message);
                    });
            });
        });
    }

This results in these lines filtered by "Web Console":

I/Web Console(14391): Verifying DB:1085
I/Web Console(14391): Verifying table undefined:1091
I/Web Console(14391): Verifying table item:1091
I/Web Console(14391): Error Verifying table undefined:no such table: undefined:1100
I/Web Console(14391): Error Verifying table item:no such table: item:1100

which is utterly strange for me


Solution

  • I finally found the problem:

    I can treat a SQLResult object in the browser like an array, but this apparently doesn't work within the phonegap build (probably because of the Android WebView browser). Therefore, no record was being inserted actually.

    I had to get rid of the jQuery loops (they don't get each item in the Phonegap app), and use a for loop instead, and eventually use the item() method of the SQLResult object:

    BAD:

    $.each(res.rows, function(i, item) {
       //...
    }
    

    GOOD:

    var item;
    for (var i = 0; i < res.rows.length; i++) {
       item = res.rows.item(i);
    }