1

I am developing an AngularJS application and found the following behavior.

I have two functions in my service. The first function returns all the categories stored in the database and the second returns one category by its id.

Here is my service:

angular.module('categoriesRepository', [])
.service('categoriesRepository', ['$cordovaSQLite', 'sqliteHelper',
    function ($cordovaSQLite, sqliteHelper) {
        //this works - returns an array with all categories
        this.getAll = function () {
            var categories = [];

            $cordovaSQLite.execute(sqliteHelper.getDb(),
                "SELECT * FROM categories;")
                .then(function (res) {
                    for (var i = 0; i < res.rows.length; i++) {
                        categories.push(res.rows[i]);
                    }
                });

            return categories;
        }

        //this works not - returns undefined
        this.getById = function (id) {
            var category;

            $cordovaSQLite.execute(sqliteHelper.getDb(),
                "SELECT * FROM categories WHERE id = ?;", [id])
                .then(function (res) {
                    category = res.rows[0];
                });

            return category;
        }

    }]);

I know that I can use Angulars $q to run functions asynchronously, and use their values when they are done processing.

Why does the getById function return the category directly and the getAll wait until the array is filled?

EDIT

I had the getAll function posted wrong. There is no return statement before $cordovaSQLite.execute

devz
  • 2,629
  • 2
  • 31
  • 37

4 Answers4

2

UPDATE:- After your question is updated.

  1. In the first example your are creating an array first by doing var categories = [];and then returning this array before finishing your async call. When your async call completes it just pushes certain elements into the array thus not destroying the reference to the array (categories ) variable. When it is returned back if you will debug it you will find the function returning an empty array and later when the async call succeeds only then the array will be filled.

  2. In the second example you are creating just a variable and then returning it before the async call finishes. But then the async call is finished you assign the variable to a new value. thus destroying the earlier reference.

Solution:- Though not a preffered approach to make it work. you will have to maintain the category variable reference. for this you can use angular.copy OR angular extend

So the second part of your code should be like this.getById = function (id) { var category;

        $cordovaSQLite.execute(sqliteHelper.getDb(),
            "SELECT * FROM categories WHERE id = ?;", [id])
            .then(function (res) {
                angular.copy(res.rows[0], category);
                //now the reference to the category variable 
                //will not be lost
            });

        return category;
    }

Better Practice:- The way you have been developing this application is wrong. Async calls should not be handled this way. I earlier asked a question just to clarify the way to handle the async calls and state inside the angular app, factories and controllers please have a look here. It provides two ways to handle the state and async calls. There might be many more practices out there but these two suit me best.

Community
  • 1
  • 1
Parv Sharma
  • 12,581
  • 4
  • 48
  • 80
  • I posted the function wrong... there is actually no return before $cordovaSQLite.execute. The only difference between both functions is just returning an array or an object. – devz Jul 01 '15 at 07:43
2

It is unfortunate that this approach appears to 'work' because it is caused by the modification of the returned array object "at some unspecified time" after it is returned.

In the usage the array is accessed/observed after1 it has been modified by the asynchronous call. This makes it appear to function correctly only because of the (accidental) asynchronous-later-than observation.

If the observation was prior to the actual completion of the SQLite operation - such as immediately after the getAll function call - it would reveal an empty array.

Both functions are incorrectly written and the first accidently releases Zalgo (or perhaps his sibling).

See How do I return the response from an asynchronous call? for more details.

1 Chrome's console.log can be confusing as it works like console.dir and thus may be showing the current value and not the value when it was invoked.

Community
  • 1
  • 1
user2864740
  • 60,010
  • 15
  • 145
  • 220
1

As stated already, this is a bad approach. You can't use result of your function immediately after it returns.

However I didn't see the answer to your exact question: why do they behave differently?

It happens because with an array you return a reference to an object (type Array). Later on you use same reference to modify contents of the object, i.e. push new items into the array.

However in second function you modify the reference itself. You make you local variable categories point to a new object. Thus old object (reference to which was returned to outer scope) remains untouched. To make it work the same way you should have written

category.row = res.rows[0];
Kirill Slatin
  • 6,085
  • 3
  • 18
  • 38
0

You return the result of the execute in the first case, whereas you return the variable in the second case, which has most likely not been populated yet.

Evan Knowles
  • 7,426
  • 2
  • 37
  • 71