I have a method in my dojo class which makes a request (say, a JSON one). If it succeeds, well and good. However, if it fails (times out or whatever), I want it to try again until it succeeds. To do this, I call the method itself in the error callback:
doReq: function(){
var req = Request(...);
return req.then(function(response, io){
// Success!
}, dojo.hitch(this, function(error, io){
this.doReq(); // Failed; try again.
}));
}
Am I doing this correctly?
It can be done this way, but you may want to limit attempts,
for example:
doReq: function(attempts){
attempts -= 1;
var req = Request(...);
return req.then(function(response, io){
// Success!
}, dojo.hitch(this, function(error, io){
if (attempts > 0) this.doReq(attempts); // Failed; try again.
else //return some error here
}));
}
I'm not sure why you return req.then(...), this will return new promise not the req's promise.
But if you want the caller of doReq to get response when the req succeeds, you can do it like this.
_request: function (deferred) {
var req = Request(...);
req.then(dojo.hitch(this, function (response, io) {
// Success!
deferred.resolve(response);
}), dojo.hitch(this, function (error, io) {
this._request(deferred); // Failed; try again.
// use deferred.reject('some error message'); to reject when it reached the retry limit, if you want to.
}));
},
doReq: function () {
var deferred = new Deferred(); // from module "dojo/Deferred"
this._request(deferred);
return deferred.promise;
}
This is how to use it.
var thePromise = this.doReq();
thePromise.then(dojo.hitch(this, function (response) {
console.log('response: ', response); // your response from deferred.resolve(response);
}), dojo.hitch(this, function (error) {
console.log('error: ', error); // your error from deferred.reject('some error message'); if you have.
}));
Related
What I am trying to get done is extend JSON object in service and then pass it to controller.
JSON came to service from another service which makes backend call.
The code is pretty complicated so I add comments and console.logs:
//get games config object from another service
gamesConfig: gamesConfigService.gamesConfig(),
// prepare name of games icons. This is support function executed in next method
transformSpace: function(subject) {
var ensuredSubject = subject.toString().toLowerCase();
var transformedSubject = ensuredSubject.replace(/ /g, '_');
return transformedSubject;
},
//add iconname property to game config object
extendGameConfig: function() {
var that = this;
this.gamesConfig
.then(function (response) {
console.log(response.data); // this works and console.log my JSON
response.data.map(function(obj) {
return new Promise(function(res){
angular.extend(obj, {
iconname: that.transformSpace(obj.attributes.name) + "_icon.png"
});
});
});
}, function () {
console.log('errror');
});
This contains one support method transformSpace and main method which is not passing data correctly. ( I think )
I'm trying to receive this promise in controller by:
theService.getGamesObj.extendGameConfig()
.then(function (response) {
$scope.allGames = response;
console.log($scope.allGames);
}, function () {
console.log('err')
});
And then I'll use it in view. For now code above doesn't work and give me following error:
TypeError: Cannot read property 'then' of undefined
I've added comments where I think your code has gone wrong
extendGameConfig: function() {
// ***********
// use => functions, that = this wont be needed
var that = this;
// ***********
// if you want this this function to return something, add a return
// this is why you get the
// Cannot read property 'then' of undefined error
// as this function returns undefined
this.gamesConfig
.then(function (response) {
console.log(response.data); // this works and console.log my JSON
// ***********
// you're using .map ... and discarding the result!
response.data.map(function(obj) {
// ***********
// you're creating a promise that never resolves!
// also, why are you promisifying synchronous code?
return new Promise(function(res){
angular.extend(obj, {
iconname: that.transformSpace(obj.attributes.name) + "_icon.png"
});
});
});
}, function () {
console.log('errror');
});
so, try this
extendGameConfig: function() {
return this.gamesConfig
.then(response => {
return response.data.map(obj => {
return angular.extend(obj, {iconname: this.transformSpace(obj.attributes.name) + "_icon.png"});
});
}, function () {
console.log('errror');
});
or, better yet
extendGameConfig: function() {
return this.gamesConfig
.then(response =>
response.data.map(obj =>
angular.extend(obj, {iconname: this.transformSpace(obj.attributes.name) + "_icon.png"})
)
)
.catch(function (err) {
console.log('error', err);
throw err; // log the error, but you'll probably want to reject this promise so the calling code doesn't think there is success?
});
}
I need to do several attempts on the async function getDBfileXHR, but I don't know how to handle this. I am new to chaining promises. Should I chain them like this?
return getDBfileXHR(dbUrl(), serverAttempts)
.then(function () { // success
console.log('-basic XHR request succeeded.');
return dbReadyDeferred.promise;
})
.catch(function (){
return getDBfileXHR(dbUrl(), serverAttempts)
.then(function (){
console.log('-basic XHR request succeeded after second attempt.');
return dbReadyDeferred.promise;
})
.catch(function () { // error
console.log("-basic XHR request failed, falling back to local DB file or localStorage DB...");
return fallbackToLocalDBfileOrLocalStorageDB();
});
})
or like that :
return getDBfileXHR(dbUrl(), serverAttempts)
.then(function () { // success
console.log('-basic XHR request succeeded.');
return dbReadyDeferred.promise;
})
.catch(function (){
if (typeof serverAttempts !== "undefined") serverAttempts++;
console.log('on passe dans le catch, serverAttempts = ', serverAttempts)
if (serverAttempts < 2) {
return getDBfileXHR(dbUrl(), serverAttempts)
.then(function () { // success
console.log('-basic XHR request succeeded.');
return dbReadyDeferred.promise;
})
.catch(function (){
console.log("-basic XHR request failed, falling back to local DB file or localStorage DB...");
return fallbackToLocalDBfileOrLocalStorageDB();
})
} else {
console.log("-basic XHR request failed, falling back to local DB file or localStorage DB...");
return fallbackToLocalDBfileOrLocalStorageDB();
}
})
This second code seems to work, but I am not sure it is best practices.
A simple and flexible solution involves creating a helper - benefit, reusable for anything that requires retrying promises:
var retryP = (fn, retry) => fn(retry).catch(err => (!isNaN(retry) && retry > 0) ? retryP(fn, retry - 1) : Promise.reject(err));
This generic function will retry fn for at most attempts number of times, passing 1 will retry once, i.e. make two attempts
your function can then be written:
var serverAttempts = 1;
// this is should be the retry attempts,
// so 0 is try at most once, 1 is at most twice etc
// argument n will be the number of retries "in hand",
// so it counts down from the passed in value to 0
return retryP(n => getDBfileXHR(dbUrl(), serverAttempts - n), serverAttempts)
.then(() => {
console.log('-basic XHR request succeeded after second attempt.');
return dbReadyDeferred.promise;
})
.catch(() => {
console.log("-basic XHR request failed, falling back to local DB file or localStorage DB...");
return fallbackToLocalDBfileOrLocalStorageDB();
});
If you aren't comfortable with ES2015+ syntax
helper:
var retryP = function retryP(fn, retry) {
return fn(retry).catch(function (err) {
return !isNaN(retry) && retry > 0 ? retryP(fn, retry - 1) : Promise.reject(err);
});
};
code:
var serverAttempts = 1;
return retryP(function (n) {
return getDBfileXHR(dbUrl(), serverAttempts - n);
}, serverAttempts)
.then(function () {
console.log('-basic XHR request succeeded after second attempt.');
return dbReadyDeferred.promise;
})
.catch(function () {
console.log("-basic XHR request failed, falling back to local DB file or localStorage DB...");
return fallbackToLocalDBfileOrLocalStorageDB();
});
I am creating a function in Node js Express, that would be called by clients to download content.
The content needs to be downloaded from disparate sources and response needs to be send back only when all downloads have completed (content downloaded by node is zipped and send back to the calling client). So, all the download functions are wrapped in Promise.all(download1(), download2(), download3())
One of the download functions not only downloads content but it also generates a json and sends it back to the main function. The main function sets it as a response header.
The API function called by client looks like this
function downloadAll(request, response) {
// Download content only after folders are created
return createDirPromise.then(function (result) {
var statusJson = ... // somehow get the status json from download1() so that it can be send to the client
return Promise.all([download1(contentsJson), download2(contentsJson), download3(contentsJson)]);
})
.then(function (result) {
return new Promise(function (resolve, reject) {
// Create zip. Here is the zip creation logic.
// Once zip is created, send it to client as a buffer
zipdir(zipFolderPath, function (err, buffer) {
if (err) {
console.log('Error while zipping!!! '+JSON.stringify(err));
return reject({ data: null, resp_status_code: 500, resp_status_message: JSON.stringify(err)});
} }
console.log('Zipping successful');
return resolve(
{
data: buffer,
resp_status_code: 200,
resp_status_message: "Zip succeeded",
headers: statusJson
});
})
})
.catch(function (error) {
return {
data: 'Error in any of above ' + error,
resp_status_code: 500,
resp_status_message: 'Error while zipping'
}
}
This is how download1 function looks like
function download1(contentsJson) {
return new Promise(function(resolve, reject) {
//set the status json here
var statusJson = { "key1": "value1", "key2": "value2"};
//do the download stuff here and return the statusJson
console.log('Downloading complete, return the status so that it can be send to the client');
resolve(statusJson);
}
I know how to send the headers back to the client. My challenge is how to get the statusJson in the downloadAll function. download1 function is called from within Promise.all(). As soon as download1, download2 and donwload3 complete '.then' is executed. I am not able to find a way to get the data (statusJson) returned by donwload1 inside downloadAll.
I am not able to find a way to get the data (statusJson) returned by donwload1 inside downloadAll.
It's the first entry in the array that you receive as result in your then callback on the Promise.all():
function downloadAll(request, response) {
return createDirPromise.then(function (result) {
return Promise.all([download1(contentsJson), download2(contentsJson), download3(contentsJson)]);
})
.then(function (result)
// **** Here, the result of download1 is result[0]
var statusJson = result[0];
Promise.all gathers together the promise results and returns them in an array in the order you gave it the promises.
(Side note: I think you're missing () after createDirPromise.)
Example:
// (Requires Promise support in your browser)
"use strict";
var createDirPromise = createPromiseFunction("createDirPromise");
var download1 = createPromiseFunction("download1");
var download2 = createPromiseFunction("download2");
var download3 = createPromiseFunction("download3");
function downloadAll(request, response) {
return createDirPromise().then(function(result) {
return Promise.all([download1(/*contentsJson*/), download2(/*contentsJson*/), download3(/*contentsJson*/)]);
})
.then(function(result) {
// **** Here, the result of download1 is result[0]
var statusJson = result[0];
console.log("statusJson = '" + statusJson + "'");
});
}
downloadAll();
function createPromiseFunction(name) {
return function() {
return new Promise(function(resolve) {
setTimeout(function() {
console.log("resolving " + name);
resolve("result of " + name);
}, Math.random() * 50);
});
};
}
I have a web page where there are a bunch of items that the user can click on. clicking on any item, depending on it's type, will send an ajax request to the server and then display more items.
If the request results in an error, I want to display it and then allow the user to continue clicking or interacting with the page as before.
My code looks like this
$scope.$createObservableFunction("clickHandler")
.flatMapLatest(function (args) {
//send the ajax request to the server
})
.retry()
.subscribe(function (data) {
//handle getting the data from the server
})
where exactly can I handle the error case? I expect errors to happen, and I always want to re-subscribe to the source, but I want a chance to handle that error.
The trick is to turn your errors into data:
$scope.$createObservableFunction("clickHandler")
.flatMapLatest(function (args) {
//send the ajax request to the server
var ajaxQuery = someObservable;
// turn the observable into
// a stream of eithers, which will
// either have a 'result'
// or an 'error'
// use .catch() to turn the error
// into a either with an error property
// use .map() to turn the success
// into a either with the result property
return ajaxQuery
.map(function (result) {
return { result: result };
})
.catch(function (error) {
return Rx.Observable.of({ error: error });
});
})
.subscribe(function (data) {
if (data.error) {
// display data.error to user
}
else {
// display data.result to user
}
})
If your ajax method returns a Promise, use then chaining:
$scope.$createObservableFunction("clickHandler")
.flatMapLatest(function (args) {
//send the ajax request to the server
var ajaxQuery = somePromise;
// turn the promise into
// a promise of eithers, which will
// either have a 'result'
// or an 'error'
return ajaxQuery
.then(function onSuccess(result) {
return { result: result };
}, function onError (error) {
return { error: error };
});
})
.subscribe(function (data) {
if (data.error) {
// display data.error to user
}
else {
// display data.result to user
}
})
I have the following project structure:
- PageController.js
- BusinessService.js
- NetworkManager.js
In my PageController I have the following call:
var getSomething = this._businessService.getMeSomething().then(function(response) {
alert("my response: " + response);
});
In my BusinessService:
getMeSometing: function() {
var user = this.getUserData();
if(user) {
var sendPromise = this._networkManager.getAnotherThing(user.id).then(function(response) {
alert("And another response: " + response);
});
} else {
$q.reject({ message: 'Rejecting this promise' });
}
},
The http calls are being made in the NetworkManager class.
So the problem here is that when I have no user data I want to break the call and so I'm using the reject.
This returns an error: Cannot read property 'then' of undefined regarding the call in PageController.
So, given my situtation, where if I have no userData, I want to cancel the request, how can I accomplish this?
Your call should return a promise. If there's a user, return the promise from getAnotherThing, otherwise, return the promise from $q.reject...
getMeSomething: function() {
var user = this.getUserData();
var sendPromise;
if(user) {
sendPromise = this._networkManager.getAnotherThing(user.id).then(function(response) {
alert("And another response: " + response);
// you probably want to return the data if successful (either response or response.data, depending on what response is)
return response.data;
});
} else {
sendPromise = $q.reject({ message: 'Rejecting this promise' });
}
return sendPromise;
}