I am not able to get chained promises to work as per RSVP documentation. I have a case where I am trying to fetch some data from the server. If for some reason an error occurs, I want to fetch the data from a local file.
I am trying to chain promises for that.
I have created a simplified example. The below example will give an output but is not what I want.
http://emberjs.jsbin.com/cobax/3
App.IndexRoute = Em.Route.extend({
model: function() {
return Ember.$.getJSON('http://test.com/search')
.then(undefined, function(errorObj, error, message) {
return new Promise(function(resolve, reject) {
resolve(model);
}).then(function(response) {
console.info(response.articles);
return response.articles;
});
});
}
});
This example is what I want but it wont call the final 'then'.
http://emberjs.jsbin.com/cobax/3
App.IndexRoute = Em.Route.extend({
model: function() {
return Ember.$.getJSON('http://test.com/search')
.then(undefined, function(errorObj, error, message) {
return new Promise(function(resolve, reject) {
resolve(model);
});
})
.then(function(response) {
console.info(response.articles);
return response.articles;
});
}
});
Basically I want to handle the server/local response from the last 'then' method. I also want keep all the callbacks in a single level.
What is the error in the second code snipped?
Update
As #marcio-junior mentioned, the jquery deferred was the issue. Here is the fixed bin from him.
http://jsbin.com/fimacavu/1/edit
My actual code doesn't return a model object, it makes another getJSON request to a json file. I can't replicate this in a bin as I dont think js bin allows us to host static files. Here is the code but it wont work. It fails due to some js error.
App.IndexRoute = Em.Route.extend({
model: function() {
var cast = Em.RSVP.Promise.cast.bind(Em.RSVP.Promise);
return cast(Ember.$.getJSON('http://test.com/search'))
.then(undefined, function(errorObj, error, message) {
//return Em.RSVP.resolve(model);
return cast(Ember.$.getJSON('data.json'));
})
.then(function(response) {
console.info(response.articles);
return response.articles;
});
}
});
Can you help me with this? These promises are a bit tricky to understand.
Here is the error stack I see
XMLHttpRequest cannot load http://test.com/search. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access. localhost/:1
Error while loading route: index ember-canary-1.7.0.js:3916
logToConsole ember-canary-1.7.0.js:3916
defaultActionHandlers.error ember-canary-1.7.0.js:39681
triggerEvent ember-canary-1.7.0.js:39763
trigger ember-canary-1.7.0.js:42317
Transition.trigger ember-canary-1.7.0.js:42162
(anonymous function) ember-canary-1.7.0.js:42017
invokeCallback ember-canary-1.7.0.js:10498
publish ember-canary-1.7.0.js:10168
publishRejection ember-canary-1.7.0.js:10596
(anonymous function) ember-canary-1.7.0.js:15975
DeferredActionQueues.flush ember-canary-1.7.0.js:8610
Backburner.end ember-canary-1.7.0.js:8082
(anonymous function)
You are returning a RSVP promise to a jquery deferred. And jquery deferreds doesn't have the feature of fulfill a rejected promise. So you need to update your sample to use Em.RSVP.Promise.cast(deferred), to transform a deferred in a RSVP promise, which implements the promises/a+ spec and does what you want:
App.IndexRoute = Em.Route.extend({
model: function() {
return Em.RSVP.Promise.cast(Ember.$.getJSON('http://test.com/search'))
.then(undefined, function() {
return getDefaultData();
})
.then(function(response) {
console.info(response.articles);
return response.articles;
});
}
});
Your updated jsbin
Here is the final route code I used. Its simply checks for the my apps api for the results. If its not present, I take the static results from a sample json file. The parsing of the response happens at the end irrespective of where it came from.
var App.IndexRoute = Ember.Route.extend({
model: function() {
var cast = Em.RSVP.Promise.cast.bind(Em.RSVP.Promise);
return cast(Ember.$.getJSON('http://test.com/search'))
.then(undefined, function(error) {
console.info(error);
return Ember.$.getJSON('assets/data.json');
})
.then(function(response) {
console.info(response);
return JSON.parse(response);
});
}
});
Related
I make Ajax requests with a Promise and usually handle errors the same way. So e.g. if a 404 happens, then I would just display a standard error message by default. But in some cases I want to do something else.
Note: I'm using ExtJS 4 to do the actual Ajax request, but this issue is not specific to ExtJS. ExtJS does not use Promises, so I'm basically converting their API to a Promise API.
This is the code:
var defaultErrorHandler = function(response) {
// do some default stuff like displaying an error message
};
var ajaxRequest = function(config) {
return new Promise(function(fulfill, reject) {
var ajaxCfg = Ext.apply({}, {
success: function(response) {
var data = Ext.decode(response.responseText);
if (data.success) {
fulfill(data);
} else {
defaultErrorHandler(response);
reject(response);
}
},
failure: function(response) {
defaultErrorHandler(response);
reject(response);
}
}, config);
Ext.Ajax.request(ajaxCfg);
});
};
// usage without special error handling:
ajaxRequest({url: '/some/request.json'}).then(function(data) {
// do something
});
// usage with special error handling:
ajaxRequest({url: '/some/request.json'}).then(function(data) {
// do something
}, function(response) {
// do some additional error handling
});
Now the problem: The "usage without special error handling" does not work, because if I do not provide a reject function, it will throw an error. To fix this, I am forced to provide an empty function, like so:
// usage without special error handling:
ajaxRequest({url: '/some/request.json'}).then(function(data) {
// do something
}, function() {});
Having to provide an empty function every time (and in my code base this will be hundreds of times) is ugly, so I was hoping there was a more elegant solution.
I also do not want to use catch() since that would catch ALL errors thrown, even if it happens in the fulfill function. But actual errors happening in my code should not be handled, they should appear in the console.
There is no such thing a "default error handler for all promises", unless you are looking to provide an unhandled rejection handler. That would however not be restricted to the promises for your ajax requests.
The simplest and best solution would be to just expose your defaultErrorHandler and have every caller explicitly pass it the then invocation on your promise. If they don't want to use it, they either need to provide their own special error handler or they will get a rejected promise. This solution provides maximum flexibility, such as allowing to handle the rejection further down the chain.
If that is not what you want to do, but instead require immediate handling of the ajax error, your best bet is to override the then method of your returned promises:
function defaultingThen(onfulfill, onreject = defaultErrorHandler) {
return Promise.prototype.then.call(this, onfulfill, onreject);
}
function ajaxRequest(config) {
return Object.assign(new Promise(function(resolve, reject) {
Ext.Ajax.request({
...config,
success: function(response) {
var data = Ext.decode(response.responseText);
if (data.success) {
resolve(data);
} else {
reject(response);
}
},
failure: reject,
});
}), {
then: defaultingThen,
});
}
I'm new to Angular.
I have this Service(?) for my RESTful services.
.factory('LanguagesService', function ($resource) {
return $resource('http://127.0.0.1:8000/language/:langId', {
langId: '#id'
});
});
Then in my controller I do like this
adminLang.addLanguage = function () {
LanguagesService.save({
code: adminLang.newCode,
name: adminLang.newName
});
}
My question is, how do I know if the save() is successful? So I can do this or that depending on if it fails or succeeds?
Thank you a lot.
When you request add two callback functions as below:
LanguagesService.save({
code: adminLang.newCode,
name: adminLang.newName
}, function(response){
// success
}, function(error){
// error
});
Check this for more information: http://fdietz.github.io/recipes-with-angular-js/consuming-external-services/consuming-restful-apis.html
$resource methods returns a promise object via $promise object, you could keep eye on that promise by placing .then.
Code
LanguagesService.save({
code: adminLang.newCode,
name: adminLang.newName
}).$promise.then(function(data){
console.log(data);
//do other awesome things
}, function(err){
});
I have a "box" route/controller as below;
export default Ember.Controller.extend({
initialized: false,
type: 'P',
status: 'done',
layouts: null,
toggleFltr: null,
gridVals: Ember.computed.alias('model.gridParas'),
gridParas: Ember.computed('myServerPars', function() {
this.set('gridVals.serverParas', this.get('myServerPars'));
this.filterCols();
if (!this.get('initialized')) {
this.toggleProperty('initialized');
} else {
Ember.run.scheduleOnce('afterRender', this, this.refreshBox);
}
return this.get('gridVals');
}),
filterCols: function()
{
this.set('gridVals.layout', this.get('layouts')[this.get('type')]);
},
myServerPars: function() {
// Code to set serverParas
return serverParas;
}.property('type', 'status', 'toggleFltr'),
refreshBox: function(){
// Code to trigger refresh grid
}
});
My route looks like;
export default Ember.Route.extend({
selectedRows: '',
selectedCount: 0,
rawResponse: {},
model: function() {
var compObj = {};
compObj.gridParas = this.get('gridParas');
return compObj;
},
activate: function() {
var self = this;
self.layouts = {};
var someData = {attr1:"I"};
var promise = this.doPost(someData, '/myService1', false); // Sync request (Is there some way I can make this work using "async")
promise.then(function(response) {
// Code to use response & set self.layouts
self.controllerFor(self.routeName).set('layouts', self.layouts);
});
},
gridParas: function() {
var self = this;
var returnObj = {};
returnObj.url = '/myService2';
returnObj.beforeLoadComplete = function(records) {
// Code to use response & set records
return records;
};
return returnObj;
}.property(),
actions: {
}
});
My template looks like
{{my-grid params=this.gridParas elementId='myGrid'}}
My doPost method looks like below;
doPost: function(postData, requestUrl, isAsync){
requestUrl = this.getURL(requestUrl);
isAsync = (isAsync == undefined) ? true : isAsync;
var promise = new Ember.RSVP.Promise(function(resolve, reject) {
return $.ajax({
// settings
}).success(resolve).error(reject);
});
return promise;
}
Given the above setup, I wanted to understand the flow/sequence of execution (i.e. for the different hooks).
I was trying to debug and it kept hopping from one class to another.
Also, 2 specific questions;
I was expecting the "activate" hook to be fired initially, but found out that is not the case. It first executes the "gridParas" hook
i.e. before the "activate" hook. Is it because of "gridParas"
specified in the template ?
When I do this.doPost() for /myService1, it has to be a "sync" request, else the flow of execution changes and I get an error.
Actually I want the code inside filterCols() controller i.e.
this.set('gridVals.layout', this.get('layouts')[this.get('type')]) to
be executed only after the response has been received from
/myService1. However, as of now, I have to use a "sync" request to do
that, otherwise with "async", the execution moves to filterCols() and
since I do not have the response yet, it throws an error.
Just to add, I am using Ember v 2.0
activate() on the route is triggered after the beforeModel, model and afterModel hooks... because those 3 hooks are considered the "validation phase" (which determines if the route will resolve at all). To be clear, this route hook has nothing to do with using gridParas in your template... it has everything to do with callling get('gridParas') within your model hook.
It is not clear to me where doPost() is connected to the rest of your code... however because it is returning a promise object you can tack on a then() which will allow you to essentially wait for the promise response and then use it in the rest of your code.
Simple Example:
this.doPost().then((theResponse) => {
this.doSomethingWith(theResponse);
});
If you can simplify your question to be more clear and concise, i may be able to provide more info
Generally at this level you should explain what you want to archive, and not just ask how it works, because I think you fight a lot against the framework!
But I take this out of your comment.
First, you don't need your doPost method! jQuerys $.ajax returns a thenable, that can be resolved to a Promise with Ember.RSVP.resolve!
Next: If you want to fetch data before actually rendering anything you should do this in the model hook!
I'm not sure if you want to fetch /service1, and then with the response you build a request to /service2, or if you can fetch both services independently and then show your data (your grid?) with the data of both services. So here are both ways:
If you can fetch both services independently do this in your routes model hook:
return Ember.RSVP.hash({
service1: Ember.RSVP.resolve($.ajax(/*your request to /service1 with all data and params, may use query-params!*/).then(data => {
return data; // extract the data you need, may transform the response, etc.
},
service2: Ember.RSVP.resolve($.ajax(/*your request to /service2 with all data and params, may use query-params!*/).then(data => {
return data; // extract the data you need, may transform the response, etc.
},
});
If you need the response of /service1 to fetch /service2 just do this in your model hook:
return Ember.RSVP.resolve($.ajax(/*/service1*/)).then(service1 => {
return Ember.RSVP.resolve($.ajax(/*/service2*/)).then(service2 => {
return {
service1,
service2
}; // this object will then be available as `model` on your controller
});
});
If this does not help you (and I really think this should fix your problems) please describe your Problem.
Here, _fact is a reference to the service.
it('Git Check', function() {
$scope.user = 'swayams'
var data;
_fact.Git($scope).then(function(d) {
expect(d.data.length).toEqual(4)
}, function() { expect(d).not.toBeNull(); });
});
I am getting the error
SPEC HAS NO EXPECTATIONS Git Check
Update
After forcing async as per #FelisCatus and adding $formDigest, I am getting a different error Error: Unexpected request: GET https://api.github.com/users/swayams/repos
No more request expected
The updated code snippet looks something like -
it('Git Check', function(done) {
$scope.user = 'swayams'
var data;
_fact.Git($scope).then(function(d) {
expect(d.data.length).toEqual(4)
}, function() { expect(d).not.toBeNull(); });
});
$rootScope.$formDigest();
I have a Plunk here illustrating the issue.
Jasmine is not seeing your expectations because your function returns before any expect() is called. Depending on your situation, you may want to use async tests, or use some promise matchers.
With async tests, you add an additional argument to your test function, done.
it('Git Check', function (done) {
$scope.user = 'swayams'
var data;
_fact.Git($scope).then(function(d) {
expect(d.data.length).toEqual(4);
}, function() { expect(d).not.toBeNull(); }).finally(done);
$rootScope.$digest();
});
(Note the finally clause in the end of the promise chain.)
Please note that you have to do $rootScope.$digest() for the promises to resolve, even if your code is not using it. See: How to resolve promises in AngularJS, Jasmine 2.0 when there is no $scope to force a digest?
I have written a rails back end to my project and when you save or create a new record,among the status 200 and a json representation of the post that was saved.
When I do the following in bacbone:
modelObject = new App.Models.Post();
modelObject.set({title: 'asdasdas', content: 'asdadasdasdasdasd'});
if (modelObject.isValid()){
modelObject.save().then( ... )
}
How do I get the post object that is returned? (assuming the post is successful).
On the rails side, when I do #post.save I also do render json: #post, status: 200 on a successful save in the create action so there is a json object coming back, I just dot know how to access it on the backbone side.
The backbone docs describe few ways how can you get response from server after calling save() function.
For example:
You need to specify error and success callbacks:
var model = new App.Models.Post();
model.set({title: 'some title', content: 'some content'});
var options = {
success: function(model, response){
console.log('success handler');
model.set({id: response.id});
},
error: function(model, xhr){
console.log('error handler');
}
};
Specify wait option to wait response from server before set model attributes:
options.wait = true;
Need to call save function with specified options:
if (model.isValid()) {
model.save({}, options);
}
The modelObject.save() call will return a promise object. You should chain a .done() call to that and pass it in a function, like this:
modelObject.save().done(function(e) {
// handle your response here
});
You could also handle a failure the same way using the .fail() function. Chain them together like this:
modelObject.save().done(function(e) {
// handle your response here
}).fail(function(e) {
// handle failure here
});
Here's another way to write the same code:
var promise = modelObject.save();
promise.done(function(e) {
// handle your response here
});
promise.fail(function(e) {
// handle failure here
});
There is also a .always() that you could chain to always be called:
var promise = modelObject.save();
promise.done(function(e) {
// handle your response here
});
promise.fail(function(e) {
// handle failure here
});
promise.always(function(e) {
// always call this on success or failure
});