Jump to content

Best practise in dealing with asynchronous functions in FrogCode


pconkie

Recommended Posts

@Chris.Smith @Graham Quince any advice please on the following?

 

I am trying to combine information from two frog api calls.  

Firstly calling getMembers to return basic information about every student in a particular group.  Secondly looping through each student and calling getDataInCategory to return extra information.  I am assuming there is no extra parameter I can add to the first call to get the extended information without making the second call! Regardless I quite like to know how you would go about beating the async issues anyway.

The issue is that the first call does not wait for the second calls to complete.  

 Frog.Model.api('groups.getMembers',{ignore_profiles: true, uuid: my_group_uuid}).done(function(thisResponse) {
	$.each(thisResponse.data, function(i, member) {
    	//loop through each member of the group
        //build object as we go
      	var thismember = {uuid: member.uuid, name: member.displayname, gender: member.gender, thumb: member.thumbnail, data:[]};
      	
    			Frog.Model.api('users.getDataInCategory',{user_uuid: member.uuid,uuid: student_flags_uuid}).done(function(thatResponse) {
                		$.each(thatResponse.data.fields, function(x, data) {
    					//loop through each field of extra data and add the data to the data array in the thismember object
                          		var key = data.label;
                                	MyContext[key] = data.value;
                        });
                  	thismember.data.push(MyContext);     //this happens after MyGroup.push(thismember)
                });
    	//the code below doesnt wait for the inner api call
    	MyGroup.push(thismember);		//this line always has 'data' empty.
    });
 });

I've read about closures, async=false and promises.  I don't understand the first and last, and async=false seems to be really bad practise,

So what's the recommended approach here?  How can i get an object out of the end of this which contains basic and extended information for each of the students in the group?

Thanks for your continued support

P

 

Edited by pconkie
Link to comment
Share on other sites

On 2017-6-6 at 09:52, pconkie said:

@Chris.Smith @Graham Quince any advice please on the following?

 

I am trying to combine information from two frog api calls.  

Firstly calling getMembers to return basic information about every student in a particular group.  Secondly looping through each student and calling getDataInCategory to return extra information.  I am assuming there is no extra parameter I can add to the first call to get the extended information without making the second call! Regardless I quite like to know how you would go about beating the async issues anyway.

The issue is that the first call does not wait for the second calls to complete.  


 Frog.Model.api('groups.getMembers',{ignore_profiles: true, uuid: my_group_uuid}).done(function(thisResponse) {
	$.each(thisResponse.data, function(i, member) {
    	//loop through each member of the group
        //build object as we go
      	var thismember = {uuid: member.uuid, name: member.displayname, gender: member.gender, thumb: member.thumbnail, data:[]};
      	
    			Frog.Model.api('users.getDataInCategory',{user_uuid: member.uuid,uuid: student_flags_uuid}).done(function(thatResponse) {
                		$.each(thatResponse.data.fields, function(x, data) {
    					//loop through each field of extra data and add the data to the data array in the thismember object
                          		var key = data.label;
                                	MyContext[key] = data.value;
                        });
                  	thismember.data.push(MyContext);     //this happens after MyGroup.push(thismember)
                });
    	//the code below doesnt wait for the inner api call
    	MyGroup.push(thismember);		//this line always has 'data' empty.
    });
 });

I've read about closures, async=false and promises.  I don't understand the first and last, and async=false seems to be really bad practise,

So what's the recommended approach here?  How can i get an object out of the end of this which contains basic and extended information for each of the students in the group?

Thanks for your continued support

P

 

Hi @pconkie,

Sorry for not getting to this sooner, I've been saddled with some emergency work which has taken over my life for the past two weeks...

Handling async functionality is not for the faint of heart and it's here where you start to see the divide between 'scripters' and programmers. In this instance, Promises are your friend. There are different specifications for Promises, but the one most commonly used and widely available in all browsers is jQuery's implementation.

As you may have worked out, Frog.Model.api returns a jQuery Promise object. When we have api's that we are racing (running at the same time) we need to collect all the promise objects and use jQuery's when function to wait for them all to complete. Here is your code, reworked using jQuery Promises.

var MyGroup = [],
    my_group_uuid = 'my_group_uuid',
    student_flags_uuid = 'student_flags_uuid';

Frog.Model.api('groups.getMembers', { ignore_profiles: true, uuid: my_group_uuid })
    .done(function(resp) {
        var members = res.data,
            requests = [];

        requests = members.map(function(member) {
            member = {
                uuid: member.uuid,
                name: member.displayname,
                gender: member.gender,
                thumb: member.thumbnail,
                data: {}
            };

            MyGroup.push(member);

            return Frog.Model.api('users.getDataInCategory', { user_uuid: member.uuid, uuid: student_flags_uuid })
                .done(function(resp) {
                    var fields = resp.data.fields || [];

                    fields.forEach(function(field) {
                        member.data[field.label] = field.value;
                    });
                });
        });

        jQuery.when.apply(this, requests)
            .done(function() {
                console.info('Yay! You now have all your data');
            });
    });

I you would prefer, you can also store the requests array out side of the first API call and also move the jQuery.when to the same level. Any code that needs to execute after the APIs have completed will need to be in the callback of the associated done function.

Here is a basic example:

// inside my fictional app
this.showLoader();
Frog.Model.api('someapi.endpoint', params)
	.always(this.hideLoader.bind(this)) // No matter the result, always remove the loading gliph
	.fail(this.handleError.bind(this)) // Deal with error state gracefully
    .done(
        function(response) {
            var data = this.transformApiData(response.data);
          
            this.render(data);
        }.bind(this)
    );

// after we have sent the request, lets set a timeout to fire in ~3 seconds if the request takes too long.
setTimeout(
    function() {
        if (this.hasLoader()) {
        	this.updateLoader("This is taking longer than anticipated");
        }
    }.bind(this),
    3000
);

The setTimeout will execute after the request is sent but before the response has been captured, regardless of how quick the network response is.

Async is something you will pickup over time; but it is definitely one of those "the more you do..." things.

 

Let me know how you get on,

 

~ Chris

/cc @Graham Quince

  • Like 1
Link to comment
Share on other sites

Amazing Chris, thanks. Makes sense.

Couple of related questions....

Does an app (not a widget) support preferences?

On smaller screens i've noticed that apps are docked/maximised when they are initialized.  Is there a way to force an app to be maximised on initialization regardless of screen size?

Thanks

Paul

 

Link to comment
Share on other sites

5 hours ago, pconkie said:

Amazing Chris, thanks. Makes sense.

Couple of related questions....

Does an app (not a widget) support preferences?

On smaller screens i've noticed that apps are docked/maximised when they are initialized.  Is there a way to force an app to be maximised on initialization regardless of screen size?

Thanks

Paul

 

Preferences aka Widget Preferences are only available for Widgets. If you let me know what you would like to do I can possibly provide you with some solutions.

With regards to maximising applications; in your application manifest:

{
  "application": {
    "args": {
      "mode": "fullscreen"
    }
  }
}

Hope that helps,

 

~ Chris

  • Like 1
Link to comment
Share on other sites

On 6/9/2017 at 15:25, Chris.Smith said:

Preferences aka Widget Preferences are only available for Widgets. If you let me know what you would like to do I can possibly provide you with some solutions.

 

I'm developing an app that needs to save a small amount of configuration data (in json format).  I've tried HTML 5 local storage but my teachers are too mobile for this!

I'm going to have to use something like firebase but thinking three steps ahead - if another school were to use this app where would they put the config string required to use their firebase? What i really need is some data storage in frog! (I bet that one hasn't been suggested before?!) Is there anywhere i can save the data in frog? It's non sensitive info... 

 Any ideas?

Paul

Link to comment
Share on other sites

2 hours ago, pconkie said:

I'm developing an app that needs to save a small amount of configuration data (in json format).  I've tried HTML 5 local storage but my teachers are too mobile for this!

I'm going to have to use something like firebase but thinking three steps ahead - if another school were to use this app where would they put the config string required to use their firebase? What i really need is some data storage in frog! (I bet that one hasn't been suggested before?!) Is there anywhere i can save the data in frog? It's non sensitive info... 

 Any ideas?

Paul

Hi Paul,

Funnily enough this has come up a few times.. :)

Unfortunately the isn't anyway currently to store arbitrary data in Frog at the moment. I will doubt check tomorrow for any loop holes or areas we can "re-use" however for the mean time an external service is your friend here. One approach when using a service like Firebase is to key the configuration data with the school uuid which is available within the majority of User Objects.

I am currently forming the specification for a project called Frog Web Services. It would be great to have a chat at some point and get your input on the proposal in-terms of functionality and features.

 

~ Chris

Link to comment
Share on other sites

Just a thought.....

On app initialisation:

1. Check for the existence of a form

2. If found get uuid and any response data

3. If not found create one and get returned uuid

 

Later any data can be saved to this form using the uuid.

just an idea but one issue off the top of my head would be...

Do the forms api allow the current users latest response to replace any previous response? This would be essential if all staff were sharing the same form. 

And how much data could be stored in say a text area field in the form? Unlimited?

I've got a bit of time today, I'll try and mock something up.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...