Execute Code only after Multiple Promises Fulfilled in EmberJS - javascript

I have an EmberJS ArrayController. I want to have a computed property on this controller, neurons, that is a subset of the model property. The subset is computed based on a toggle button in the sidebar which is bound to currentDataset. Another computed property, activePlots then depends on neurons; the Neuron model has a hasMany relationship to Plot, and activePlots loads all the plot objects associated with each neuron object in neurons.
Currently I'm trying to do this with mapBy, but I'm running into a problem. Each retrieval of a Neuron object's plots returns a PromiseArray. I need to manipulate all the returned plots at once. I understand I can call then on the promise result of an individual call get('plots'), but how do I execute code only after the get('plots') call has returned for ALL neurons?
neurons: ( ->
#get('model').filterBy('dataset', #get('currentDataset'))
).property('model', 'currentDataset'),
activePlots: ( ->
plots = #get('neurons').mapBy('plots')
# ...code to execute after all plots have loaded
).property('neurons')
UPDATE: Picture of console output from console.log(plotSets) inside the then callback to
Ember.RSVP.all(#get('neurons').mapBy('plots')).then (plotSets) ->
console.log(plotSets)

There is a handy method for combining promises: Ember.RSVP.all(ary) takes an array of promises and becomes a promise that is resolved when all the promises in the input array are resolved. If one is rejected, the all() promise is rejected.
This is very handy, for example, when firing off multiple parallel network requests and continuing when all of them are done.

In addition to what Steve said you can watch the nuerons.length and use Ember.scheduleOnce to schedule an update (guessed coffeescript below)
activePlots: [],
watchNuerons: ( ->
Ember.run.scheduleOnce('afterRender', this, #updatePlots);
).observes('nueron.length'),
updatePlots: ( ->
plots = #get('neurons').mapBy('plots')
# ...code to execute after all plots have loaded
#set('activePlots', plots)
)

Related

Observable fires only once

I decided to pick up this RxJS tutorial over the weekend to learn about reactive programming. The goal is to set up a simple page that generates an interactive list of users from the Github users API using Observables.
The list displayed is a subset of the total number of users retrieved (in my case 3 out of 30). The list needs to be refreshable (show a new set of users), and you need to be able to remove entries from it by clicking the 'remove' button on each respective entry.
I've set up a chain of Observables to define the behavior of the page. Some act as events to trigger processing, and some publish processed results for use in the app. This chain should dynamically cause my list to be updated. Currently, the default flow is like this:
Startup!
Suggestions refresh triggered! (this is where the data is retrieved)
30 new suggestions available!
List update triggered! (this is where I choose 3 users to display)
List updated! (at this point, the list is showing on the page)
A list update is triggered on startup, by refreshing the list and by removing something from the list. However, when I refresh the list, this happens:
Refresh button clicked!
Suggestions refresh triggered!
30 new suggestions available!
As you can see, the trigger to update the list of users is not set off. As I understand it, by emitting some value at the start of a stream, the rest of the stream should be executed consequently. However, this only seems to be happening the first time I run through the chain. What am I missing?
You can find a running version of my project here.
I think the issue is in the way userStream$ Observable is created.
Filtering users not closed and then taking the first 3 is something that can be done directly on the UserModel[] array passed into the pipe chain by displayEvents$ via filter and slice methods of Array.
If you do so, you remove the need of using the from function to create an Observable<UserModel> on which you then have to apply flatMap (which is currently better known as mergeMap) to apply finally toArray to transform it back into an Array of UserModel.
In other words you can simplify the code as in the following example, which as side effect solves the refresh problem.
this.userStream$ = this.displayEvent$.pipe(
map(users => users
.filter((user: UserModel) => !this.closedUsers.has(user))
.slice(0, this.numberOfUsers))
// flatMap((users: UserModel[]) => from(users))
// // Don't include users we've previously closed.
// , filter((user: UserModel) => !this.closedUsers.has(user))
// , take(this.numberOfUsers)
// , toArray()
, tap(() => console.log('List updated!'))
// Unless we explicitly want to recalculate the list of users, re-use the current result.
, shareReplay(1));
To be honest though I have not fully grasped why your original solution, which is a sort of long detour, does not work.

Using the .find().fetch() from within a function in Meteor

I am making a project with Meteor and I'm having some issues trying to get data out of mongodb in JavaScript. I have the following in a function:
console.log(Time.find({today: "Saturday"}).fetch());
In my publish.js file on the server side I have the following:
Meteor.publish("time", function () {
var currentUserId = this.userId;
return Time.find({user: currentUserId});
});
And In my subscriptions file I have the following:
Meteor.subscribe("time");
This function gets called later down in the code but it returns an empty array. If I run this code in my browsers console it returns an array with 2 objects in it, which is correct. This leads me wondering if I can use the .fetch() function from within my code? As if I leave off the .fetch() it returns what looks like the usual giant object. My real problem is I need the data in the form that .fetch() gives it to me in. I think it's because the function gets triggered before the data gets a chance to load in, as if I switch out the .fetch() for a .count() it returns 0.
Is there any way around this or a fix?
Where are you you running that console.log?
There are a couple fundementals here that I believe you may have glossed over.
1 Pub / Sub
This is how we get data from the server, when we subscribe to a publication i becomes active and begins to send data, this is neither instant or synchronous, (think of it more like turning on a hose pipe), so when you run your console.log, you may not yet have the data on the client.
2 Reactive contexts
One of the fundamental aspects to building anything in meteor is its reactivity. and it helps to start thinking in terms of reactive and non reactive contexts. A reactive context is one that re-runs each time the data it depends on changes. Using an autorun (Tracker.autorun or this.autorun insdie a template lifecycle callback) or a template helper are good examples. By placing it in a template helper it will re-run when the data is available.
Template.Whatever.helpers({
items: function() {
// ...do your find here.....
}
});
As items is a reactive context, depending on the collection data, it re-run when that changes, giving you access to the data when the client has them.
3 Retrieving Non Reactive Data
Alternatively it is also possible to retrieve data non-reactively by using Meteor.call with a meteor method, and then doing something with the result, in the callback to the Meteor.call. Depending on what you're doing, Meteor.wrapAsync may also be your friend here.
a simple example (out of my head, untested) :
// on the server
Meteor.methods({
gimmeStuff: function() {
return "here is your stuff kind sir!";
}
});
// on the client
Meteor.call('gimmeStuff', function(err, result) {
if (err || !result) {
console.log("there was an error or no result!");
return false;
}
console.log(result);
return result;
});
4 Its Unlikely that you actually need ithe .fetch()
If you're working with this in a template, you don't need a fetch.
If you want this to be non-reactive you don't need a fetch
As one of the commenters mentioned, a cursor is just a wrapper around that array, giving you convenient methods, and reactivity.
5 Go Back to the Begining
If you haven't already, I would highly recommend working through the tutorial on the meteor site carefully and thoroughly, as it covers all of the essentials you'll need to solve far more challenging problems than this, as well as, by way of example, teach you all of the fundamental mechanics to build great apps with Meteor.

AngularJS $http in nested foreach

I imagine that a solution is going to involve some form of promises although I'm struggling to get my head around how I would implement promises in this scenario.
Essentially I have a service which returns $resource, I then use .query() in the controller to get the array of site contexts. The query callback function then passes the response to a $scope function ($scope.getTaskLists).
The getTaskLists function loops through the site contexts using angular.forEach(). For every loop iteration it will use $http to return any task lists within each site context. Using the .success() promise, each $http request calls another for each loop, this time looping through the $http response (task lists). For each task list, another $http request is called which gets the root folder as we need a parameter later on. The .success() promise for this $http call extracts the parameter and then calls $scope.getTaskItems, passing in various parameters including site context and list id.
The getTaskItems function then uses the above parameters to make an $http request to the list, which will return all the list items. The .success() callback here then loops through all the items and pushes the objects to the task scope.
Ultimately the structure resembles something along these lines:
- $resource.query() // get site contexts
- angular.forEach(sites)
- $http().success(... // get task lists
- angular.forEach(taskLists)
- $http().success(... // get root context
- $http().success(... // get task items
- angular.forEach(taskItems)
- $scope.tasks.push(taskItem) // push task item to $scope
What I need to do is run some code once all task items have been pushed to $scope.tasks. Is this possible using promises and is there a better way of streamlining the above code so that it isn't mangled spaghetti code consisting of ajax requests and loops?
Thanks
In my opinion your angular workflow is good. The main problem may be on the server side.
If you need ALL your nested resources. Juste expose it in your json.
In your case you have something like :
[
{"id":"1", "name":"siteContext1"},
{"id":"2", "name":"siteContext2"}
]
And probably this kind of structure for all your resources. But in your specific case your first call should meet all you needs instead of giving you only a few information about the resource.
You probably need something like this :
[
{
"id":"1",
"tasks":[{
"id":"1",
"rootContext": [{...}]
}],
"name":"siteContext1",
},
{
"id":"2",
"tasks":[{
"id":"2",
"rootContext": [{...}]
}],
"name":"siteContext2",
}
]
If you can't modify your API. Then you're actually doing it in a good way according to your API ... in my humble opinion.
Actually $http calls return promises. success() is a function fired when the promise is resolved.
Hope it helped you.
can't you change the back end called by this:
$resource.query() // get site contexts
to get you all the:
$http().success(... // get task items
you need ?
Otherwise it seems to me to be a classical flow/usecase of the promises.

Best Practises - BackBone JS - Model View Relationship

I need some suggestions around what should be the best-practice for the below scenario :
1. *Who (Model or View ) should hold the promise when a "FETCH" is called upon the Model ?*
For example :
Model FOO --> initialize: { .....this.promise = this.fetch(); }
View view --> someWhere: { var foo=new Foo(); foo.promise.done(..do something here) }
OR
Model FOO --> initialize: { ... }
View view --> someWhere: { var foo=new Foo();
var myPromise=foo.fetch();myPromise.done(..do something..) }
....
2. Second Question is around whether Initializing a Model
should automatically call the fetch ? ( As noted in the above Example )
There is no harm in doing either ways but I am not able to figure pros-cons of over each either style. Your opinion is appreciated
All anwers are in the official docs.
1) You don't need to hold that promise unless you going to use it later in other methods (which looks strange).
fetch method returns a jqXHR which is exactly a promise (Deferred). You can use it immediately in the place where you call fetch without holding a promise instance.
E.g.:
model.fetch().done(..do something here..);
2) Quote from the docs:
When your app first loads, it's common to have a set of initial models
that you know you're going to need, in order to render the page.
Instead of firing an extra AJAX request to fetch them, a nicer pattern
is to have their data already bootstrapped into the page. You can then
use reset to populate your collections with the initial data.

How to return a promise composed of nested models in EmberJS with EmberData?

Enviroment
# Ember : 1.4.0
# Ember Data : 1.0.0-beta.7+canary.b45e23ba
Model
I have simplified my use case to make the question easier to understand and anwser. Let's assume we have 3 models: Country, Region and Area:
Country:
- id: DS.attr('number')
- name: DS.attr('string')
- regions: DS.hasMany('region')
Region:
- id: DS.attr('number')
- name: DS.attr('string')
- country: DS.belongsTo('country')
- areas: DS.hasMany('area')
Area:
- id: DS.attr('number')
- name: DS.attr('string')
- region: DS.belongsTo('region')
Expected results
The Route's model hook should return an array of objects. Like this:
Note: The indentations are only to make the example more readable.
Country I
Region A
Area 1
Area 2
Region B
Area 3
Country II
Region C
Area 4
Country III
Region D
Area 5
Current approach
App.MyRoute = Ember.Route.extend({
model: function() {
return this.store.find('country').then(function(countries){
// promise all counties
// map resolved countires into an array of promises for owned regions
var regions = countries.map(function(country){
return country.get('regions');
});
// map resolved regions into an array of promises for owned areas
var areas = regions.then(function(regions){
return regions.map(function(region){
return region.get('areas');
});
});
// do not return until ALL promises are resolved
return Ember.RSVP.all(countries, regions, areas).then(function(data){
// here somehow transform the data into expected output format and return it
});
});
}
)};
Error
I'm getting Error while loading route: TypeError: Object [object Array] has no method 'then' which obviously comes from this code:
var regions = countries.map(function(country){
return country.get('regions');
});
var areas = regions.then(function(regions){
// regions is not a promise
However this should show the real problem I have:
The problem
I need countries resolved to get the regions, which in turn I need to get the areas. I've been checking the RSVP.hash and RSVP.all functions, reading the official API and watching this talk, however I somewhat fail to create the correct code to chain promises and in the final then modify the returned result to match my expectations.
Final thoughts
I have been told that loading data like this may cause many HTTP requests and probably this would be solved better by sideloading, but:
at this moment, I use FixturesAdapter, so HTTP requests are not an issue
I really want to understand RSVP and Promises better
Thats why it is important to me to figure out how this should be done correctly.
Edit 1: applying changes suggested by kingpin2k
I've created a JSBin for my example with changes suggested by kingpin2k's anwser.
While the code works, the results are... unexpected:
in the countries array I found both country and region objects. Why?
the country and region objects seem to be loaded, but area's don't (see console log results in the JSBin).. Why?
Edit 2: Explanation of unexpected behaviour from Edit1.
So I've finally noticed where I went astray from the righteous path of Ember. Kingpin2k's anwser was a huge step forward, but it contains a little error:
return this.store.find('country').then(function(countries){
// this does not return an array of regions, but an array of region SETs
var regionPromises = countries.getEach('regions');
// wait for regions to resolve to get the areas
return Ember.RSVP.all(regionPromises).then(function(regions){
// thats why here the variable shouldn't be called "regions"
// but "regionSets" to clearly indicate what it holds
// for this example i'll just reassign it to new var name
var regionSets = regions;
// now compare these two lines (old code commented out)
//var areaPromises = regions.getEach('areas');
var areaPromises = regionSets.getEach('areas');
// since regionSet does not have a property "areas" it
// won't return a promise or ever resolve (it will be undefined)
// the correct approach would be reduceing the array of sets
// an array of regions
var regionsArray = regionSets.reduce(function(sum, val){
// since val is a "Ember.Set" object, we must use it's "toArray()" method
// to get an array of contents and then push it to the resulting array
return sum.pushObjects(val.toArray());
}, []);
// NOW we can get "areas"
var realAreaPromises = regionsArray.getEach('areas');
// and now we can use Ember.RSVP to wait for them to resolve
return Ember.RSVP.all(realAreaPromises).then(function(areaSets){
// note: here again, we don't get an array of areas
// we get an array of area sets - each set for the corresponding region
var results = [];
So.. now I've finally got all the objects correctly resolved (countries, regions, areas) and can continue my work :)
EDIT 3: Working solution in this JSBin!
The trick is you need to resolve certain promises before you can access the properties on those records. Ember.RSVP.all takes an Array of promises. Ember.RSVP.hash takes a hash of promises. Unfortunately you're in the situation where you can't construct your promises until the previous promises have resolved (a la, you don't know which regions to get until the countries are resolved, and you don't know which areas to get until the regions are resolved). That being the case you really have a serial set of promises to fetch (albeit arrays of promises at each level). Ember knows to wait until the deepest promise has resolved and to use that value as the model.
Now we need to pretend that regions and area are async, if they aren't, you're telling Ember Data the information will be included in the request with country, or in the request with region and those collections won't be promises so the code I've included below wouldn't work.
regions: DS.hasMany('region', {async: true})
areas: DS.hasMany('area', {async: true})
App.IndexRoute = Ember.Route.extend({
controllerName: 'application',
model: function() {
return this.store.find('country').then(function(countries){
// get each country promises
var regionCollectionPromises = countries.getEach('regions');
// wait for regions to resolve to get the areas
return Ember.RSVP.all(regionCollectionPromises).then(function(regionCollections){
var regions = regionCollections.reduce(function(sum, val){
return sum.pushObjects(val.toArray());
}, []);
var areaCollectionPromises = regions.getEach('areas');
//wait on the areas to resolve
return Ember.RSVP.all(areaCollectionPromises).then(function(areaCollections){
// yay, we have countries, regions, and areas resolved
return countries;
});
});
});
}
});
All this being said, since it appears you're using Ember Data, I'd just return this.store.find('country') and let Ember Data fetch the data when it's used... This template would work without all of that promise code, and would populate as Ember Data fulfill's the promises on its own (it will request the data once it sees you've attempted to use the data, good ol' lazy loading).
{{#each country in model}}
Country: {{country.name}}
{{#each region in country.regions}}
Region: {{region.name}}
{{#each area in region.areas}}
Area: {{area.name}}
{{/each}}
{{/each}}
{{/each}}
What you could do instead:
If you're here, you're probably doing the same mistake as I did :)
If you need a complicated tree of objects to display your route, you could also:
If you use RESTAdapter you could sideload data in one HTTP request.
If you use FixturesAdapter (eg. in development phase) with a fixed set of data, you could switch to LocalStorageAdapter - as when you request a model, it loads all the associated models. So it will be as easy as a simple this.store.find('mymodel', model_id)
However I'm leaving the original anwser marked as "accepted" as it actually anwsers the original question, and this anwser is just a note for future reference/other users.

Categories

Resources