Angularjs Chaining Promises and $q [duplicate] - javascript

Following Approach works:
$q.when()
.then(checkCookieToken) // check if cookie already exists e.g. in cookie
.then(setHeader) // set Header with REST-Token e.g from cookie
.then(checkTokenOnline) // if not OK logout
.then(getMenu) // if previous OK get navigation menu
.then(getDataResource) // set ngResource
.then(getData); // and query it
4 Questions:
1) If e.g. checkTokenOnline is not OK, I don't want to execute the rest functions, how can I quit (exit, break, whatever,..) at this point?
2) How can I set some of them parallel and some of them serial ?
3) How can I transfer data between them?
4) How can I make the following function dependend from its previous result?

You are asking how to chain functions in promises.
3) How can I transfer data between them?
4) How can I make the following function depend on its previous result?
Return data (or a promise) for the next function in the chain:
var p2 = p1.then ( function (data) {
var nextData = someFn(data);
return nextData;
});
var p3 = p2.then ( function (nextData) {
var nextData2 = someOtherFn(nextData);
return nextData2;
});
//return for further chaining
return p3;
1) If e.g. checkTokenOnline is not OK, I don't want to execute the rest functions, how can I quit (exit, break, whatever,..) at this point?
To reject a promise, have your function throw an error. The chain will skip all the .then methods until you supply an error handler.
var p2 = p1.then ( function checkTokenOnline (response) {
if ( isBadFn(response) {
throw error;
} else {
return nextData;
}
}) .then ( someFn
) .then ( someOtherFn
) .catch ( function (error) {
// someFn and someOtherFn skipped
//log error
throw error;
});
//return for further chaining
return p2;
2) How can I set some of them parallel and some of them serial ?
To make two functions run in parallel, make two promises. Use $q.all to wait for them both to complete.
var p1 = $q.when ( fn1() );
var p2 = $q.when ( fn2() );
var p3 = $q.all ( [p1, p2] );
var p4 = p3.then ( function (responseList) {
var response1 = responseList[0];
var response2 = responseList[1];
return something;
}). catch ( function (error) {
//log error
throw error;
});
//return for further chaining
return p4;
Be aware that $q.all is not resilient. If any promise throws an error, the .then method will be skipped and only the first error will go to the .catch method.
The rule of thumb for functional programming is always return something.
Useful links
AngularJS $q Reference - Chaining promises
You're Missing the Point of Promises.
Ninja Squad -- Traps, anti-patterns and tips about AngularJS promises

Related

Chaining Promises and Passing Parameters between Them

I'm new to Node/Express and am trying to use Promises to executive successive API calls to Apple's CloudKit JS API.
I'm unclear on how to put the functions in sequence and pass their respective return values from one function to the next.
Here's what I have so far:
var CloudKit = require('./setup')
//----
var fetchUserRecord = function(emailConfirmationCode){
var query = { ... }
// Execute the query
CloudKit.publicDB.performQuery(query).then(function (response) {
if(response.hasErrors) {
return Promise.reject(response.errors[0])
}else if(response.records.length == 0){
return Promise.reject('Email activation code not found.')
}else{
return Promise.resolve(response.records[0])
}
})
}
//-----
var saveRecord = function(record){
// Update the record (recordChangeTag required to update)
var updatedRecord = { ... }
CloudKit.publicDB.saveRecords(updatedRecord).then(function(response) {
if(response.hasErrors) {
Promise.reject(response.errors[0])
}else{
Promise.resolve()
}
})
}
//----- Start the Promise Chain Here -----
exports.startActivation = function(emailConfirmationCode){
CloudKit.container.setUpAuth() //<-- This returns a promise
.then(fetchUserRecord) //<-- This is the 1st function above
.then(saveRecord(record)) //<-- This is the 2nd function above
Promise.resolve('Success!')
.catch(function(error){
Promise.reject(error)
})
}
I get an error near the end: .then(saveRecord(record)) and it says record isn't defined. I thought it would somehow get returned from the prior promise.
It seems like this should be simpler than I'm making it, but I'm rather confused. How do I get multiple Promises to chain together like this when each has different resolve/reject outcomes?
There are few issues in the code.
First: you have to pass function to .then() but you actually passes result of function invocation:
.then(saveRecord(record))
Besides saveRecord(record) technically may return a function so it's possible to have such a statement valid it does not seem your case. So you need just
.then(saveRecord)
Another issue is returning nothing from inside saveRecord and fetchUserRecord function as well.
And finally you don't need to return wrappers Promise.resolve from inside .then: you may return just transformed data and it will be passed forward through chaining.
var CloudKit = require('./setup')
//----
var fetchUserRecord = function(emailConfirmationCode){
var query = { ... }
// Execute the query
return CloudKit.publicDB.performQuery(query).then(function (response) {
if(response.hasErrors) {
return Promise.reject(response.errors[0]);
}else if(response.records.length == 0){
return Promise.reject('Email activation code not found.');
}else{
return response.records[0];
}
})
}
//-----
var saveRecord = function(record){
// Update the record (recordChangeTag required to update)
var updatedRecord = { ... }
return CloudKit.publicDB.saveRecords(updatedRecord).then(function(response) {
if(response.hasErrors) {
return Promise.reject(response.errors[0]);
}else{
return Promise.resolve();
}
})
}
//----- Start the Promise Chain Here -----
exports.startActivation = function(emailConfirmationCode){
return CloudKit.container.setUpAuth() //<-- This returns a promise
.then(fetchUserRecord) //<-- This is the 1st function above
.then(saveRecord) //<-- This is the 2nd function above
.catch(function(error){});
}
Don't forget returning transformed data or new promise. Otherwise undefined will be returned to next chained functions.
Since #skyboyer helped me figure out what was going on, I'll mark their answer as the correct one.
I had to tweak things a little since I needed to pass the returned values to subsequent functions in my promise chain. Here's where I ended up:
exports.startActivation = function(emailConfirmationCode){
return new Promise((resolve, reject) => {
CloudKit.container.setUpAuth()
.then(() => {
return fetchUserRecord(emailConfirmationCode)
})
.then((record) => {
resolve(saveRecord(record))
}).catch(function(error){
reject(error)
})
})
}

Try and catch around promise

I need to write a function that returns a promise, where first I call a synchronous function A() which returns some result.
Then return a function B(result) where B is a promise which takes in the result of A().
If either function fails I want the same error function C(error) to get called where C is a promise.
What is the best way of writing this. This is what I have but think there is obvious way I am missing
function() {
try {
var result = A();
return B(result)
.catch(function(error) {
return C(error);
});
}
catch(error) {
return C(error);
}
}
It seems wrong combining synchronous try and catch with a promise .catch and also wrong there are two different places I need to call C(error).
A() throws an error rather than returning an error code.
You don't say exactly how A() fails. It could either throw or it could return an error result. I'll show a scheme for both. The key to a mix of sync and async is to always return a promise. This will give you a consistent interface for teh caller no matter how the function succeeds or fails.
If you are only worried about A() throwing an exception and it doesn't return an error code, then you can do this:
function someFunction() {
try {
var result = A();
return B(result);
} catch(err) {
return Promise.reject(err);
}
}
someFunction().then(function(result) {
// code here to process the final result
}).catch(C);
If you also have the case where A() can return an error code, then you can do this:
function someFunction() {
try {
var result = A();
// check for error value
if (result < 0) {
throw result;
}
return B(result);
} catch(err) {
return Promise.resolve(err);
}
}
Note that both of these patterns avoid creating an extra promise if it isn't needed. They only create the extra promise when returning an error that occurred synchronously.
The Bluebird promise library has a helper function for this particular circumstance called Promise.method. The utility of Promise.method() is that it automatically wraps your function in a try/catch handler and if there are any synchronous exceptions thrown, it automatically turns them into returning a rejected promise. You could use it like this:
var someFunction = Promise.method(function() {
var result = A();
// check for error condition
if (result < 0) {
throw result;
}
return B(result);
});
someFunction().then(function(result) {
// code here to process the final result
}).catch(C);
I'm assuming that both A and B can throw errors here. Using the standard API it could look like this:
function() {
return new Promise((resolve, reject) => {
try {
resolve(A());
} catch (error) {
reject(error);
}
})
.then(B)
.catch(C);
}
This will return a promise that's either resolved with the output of B or the output of C, if that provides a fallback. You can also consider handling any errors outside of this function if that makes sense for your use case.
When using Bluebird this should also be possible:
function() {
return Promise.method(A)().then(B).catch(C)
}
I think a good way to do this would be to still use promises for the synchronous function. It keeps it consistent within a function, especially if you want something to respond to the success like a pseudo-promise. But the key is that you'd use an immediately resolved promise. Take a look at this blog post on ES6 Promises.
// an immediately resolved promise
var a = Promise.resolve(A());
assuming you've already created the promise and defined C like so:
var B = new Promise(function(resolve, reject) {
if (a) {
resolve('success'); // fulfilled successfully
}
else {
C('rejected'); // error, rejected
}
})
.then((result) => {console.log('made it!');})
.catch((result) => {C('rejected');});
var C = (err)=>{console.log('error: ' + err); return err;}
this code should do what you want:
a.then((result) => B(result));
^ this last line is the most important, since it uses the output for A to call B

Q Promise chain and NodeJS callbacks

I think this is a really stupid question but I'm having a hard time wrapping my head around promises.
I'm using Q (for nodejs) to sync up a couple of async functions.
This works like a charm.
var first = function () {
var d = Q.defer();
fs.readdir(path,function(err,files){
if(err) console.log(err);
d.resolve(files);
});
return d.promise;
};
var second = function (files) {
var list = new Array;
files.forEach(function(value, index){
var d = Q.defer();
console.log('looking for item in db', value);
db.query(
'SELECT * FROM test WHERE local_name =? ', [value],{
local_name : String,
},
function(rows) {
if (typeof rows !== 'undefined' && rows.length > 0){
console.log('found item!', rows[0].local_name);
d.resolve(rows[0]);
} else {
var itemRequest = value;
getItemData(itemRequest);
}
}
);
list.push(d.promise);
});
return Q.all(list);
};
first()
.then(second)
.done(function(list){
res.send(list);
});
The problem I have is with this little function:
getItemData(itemRequest)
This function is filled with several of callbacks. The promise chain runs through the function just fine but ignores all the callbacks I use ( eg several XHR calls I make in the function).
A simplified version of the function looks like this (just to give you an idea):
function getItemData(itemRequest){
helper.xhrCall("call", function(response) {
var requestResponse = JSON.parse(response)
, requestInitialDetails = requestResponse.results[0];
downloadCache(requestInitialDetails,function(image) {
image = localImageDir+requestInitialDetails.image;
helper.xhrCall("call2", function(response) {
writeData(item,image,type, function(){
loadData(item);
});
});
} else {
writeData(item,image,type, function(){
loadData(item);
});
}
});
});
The xhr function I use looks like this:
xhrCall: function (url,callback) {
var request = require("request")
, colors = require('colors');
request({
url: url,
headers: {"Accept": "application/json"},
method: "GET"
}, function (error, response, body) {
if(!error){
callback(body);
}else{
console.log('Helper: XHR Error',error .red);
}
});
}
So my questions:
Can I leave the function unaltered and use the callbacks that are in place ánd the promise chain?
Or do I have to rewrite the function to use promises for the XHR?
And if so, How can I best write my promise chain? Should I reject the initial promise in the forEach?
Again, sorry if this is a really stupid question but I don't know what the right course of action is here.
Thanks!
[EDIT] Q.nfcall, I don't get it
So I've been looking into Q.nfcall which allows me to use node callbacks. Bu I just don't understand exacly how this works.
Could someone give a simple example how I would go about using it for a function with several async xhr calls?
I tried this but as you can see I don't really understand what I'm doing:
var second = Q.nfcall(second);
function second (files) {
[EDIT 2]
This is the final funcction in my getitemdata function callback chain. This function basically does the same as the function 'second' but I push the result directly and then return the promise. This works as stated, but without all the additional callback data, because it does not wait for the callbacks to return with any data.
function loadData(item) {
var d = Q.defer();
db.query(
'SELECT * FROM test WHERE local_name =? ', [item],{
local_name : String,
},
function(rows) {
if (typeof rows !== 'undefined' && rows.length > 0){
list.push(d.promise);
}
}
);
});
return Q.all(list);
};
Your answer is not really clear after your second edit.
First, on your orignal question, your getItemData has no influence on the promise chain.
You could change you the function's call signature and pass your deferred promise like so.
getItemData(itemRequest, d)
and pass this deferred promises all the way to your xhrCall and resolve there.
I would re-write your whole implementation and make sure all your functions return promises instead.
Many consider deferred promises as an anti-pattern. So I use use the Promise API defined in harmony (the next javascript)
After said that, I would re-implement your original code like so (I've not tested)
var Promise = Promise || require('es6-promise').Promise // a polyfill
;
function errHandler (err){
throw err
}
function makeQuery () {
var queryStr = 'SELECT * FROM test WHERE local_name =? '
, queryOpt = {local_name: String}
;
console.log('looking for item in db', value)
return new Promise(function(resolve, reject){
db.query(queryStr, [value], queryOpt, function(rows) {
if (typeof rows !== 'undefined' && rows.length > 0){
console.log('found item!', rows[0].local_name);
resolve(rows[0]);
} else {
// note that it returns a promise now.
getItemData(value).then(resolve).catch(errHandler)
}
})
})
}
function first () {
return new Promise(function(resolve, reject){
fs.readdir(path, function(err, files){
if (err) return reject(err)
resolve(files)
})
})
}
function second (files) {
return Promise.all(files.map(function(value){
return makeQuery(value)
});
}
first()
.then(second)
.then(res.send)
.catch(errHandler)
Note that there is no done method on the Promise API.
One down side of the new Promise API is error handling. Take a look at bluebird.
It is a robust promise library which is compatible with the new promise API and has many of the Q helper functions.
As far as I can tell, you need to return a promise from getItemData. Use Q.defer() as you do in second(), and resolve it when the callbacks complete with the data. You can then push that into list.
To save code, you can use Q.nfcall to immediately call a node-style-callback function, and return a promise instead. See the example in the API docs: https://github.com/kriskowal/q/wiki/API-Reference#qnfcallfunc-args

Resolving a deferred using Angular's $q.when() with a reason

I want to use $q.when() to wrap some non-promise callbacks. But, I can't figure out how to resolve the promise from within the callback. What do I do inside the anonymous function to force $q.when() to resolve with my reason?
promises = $q.when(
notAPromise(
// this resolves the promise, but does not pass the return value vvv
function success(res) { return "Special reason"; },
function failure(res) { return $q.reject('failure'); }
)
);
promises.then(
// I want success == "Special reason" from ^^^
function(success){ console.log("Success: " + success); },
function(failure){ console.log("I can reject easily enough"); }
);
The functionality I want to duplicate is this:
promises = function(){
var deferred = $q.defer();
notAPromise(
function success(res) { deferred.resolve("Special reason"); },
function failure(res) { deferred.reject('failure'); }
);
return deferred.promise;
};
promises.then(
// success == "Special reason"
function(success){ console.log("Success: " + success); },
function(failure){ console.log("I can reject easily enough"); }
);
This is good, but when() looks so nice. I just can't pass the resolve message to then().
UPDATE
There are better, more robust ways to do this. $q throws exceptions synchronously, and as #Benjamin points out, the major promise libs are moving toward using full Promises in place of Deferreds.
That said, this question is looking for a way to do this using $q's when() function. Objectively superior techniques are of course welcome but don't answer this specific question.
The core problem
You're basically trying to convert an existing callback API to promises. In Angular $q.when is used for promise aggregation, and for thenable assimilation (that is, working with another promise library). Fear not, as what you want is perfectly doable without the cruft of a manual deferred each time.
Deferred objects, and the promise constructor
Sadly, with Angular 1.x you're stuck with the outdated deferred interface, that not only like you said is ugly, it's also unsafe (it's risky and throws synchronously).
What you'd like is called the promise constructor, it's what all implementations (Bluebird, Q, When, RSVP, native promises, etc) are switching to since it's nicer and safer.
Here is how your method would look with native promises:
var promise = new Promise(function(resolve,reject){
notAPromise(
function success(res) { resolve("Special reason") },
function failure(res) { reject(new Error('failure')); } // Always reject
) // with errors!
);
You can replicate this functionality in $q of course:
function resolver(handler){
try {
var d = $q.defer();
handler(function(v){ d.resolve(v); }, function(r){ d.reject(r); });
return d.promise;
} catch (e) {
return $q.reject(e);
// $exceptionHandler call might be useful here, since it's a throw
}
}
Which would let you do:
var promise = resolver(function(resolve,reject){
notAPromise(function success(res){ resolve("Special reason"),
function failure(res){ reject(new Error("failure")); })
});
promise.then(function(){
});
An automatic promisification helper
Of course, it's equally easy to write an automatic promisification method for your specific case. If you work with a lot of APIs with the callback convention fn(onSuccess, onError) you can do:
function promisify(fn){
return function promisified(){
var args = Array(arguments.length + 2);
for(var i = 0; i < arguments.length; i++){
args.push(arguments[i]);
}
var d = $q.defer();
args.push(function(r){ d.resolve(r); });
args.push(function(r){ d.reject(r); });
try{
fn.call(this, args); // call with the arguments
} catch (e){ // promise returning functions must NEVER sync throw
return $q.reject(e);
// $exceptionHandler call might be useful here, since it's a throw
}
return d.promise; // return a promise on the API.
};
}
This would let you do:
var aPromise = promisify(notAPromise);
var promise = aPromise.then(function(val){
// access res here
return "special reason";
}).catch(function(e){
// access rejection value here
return $q.reject(new Error("failure"));
});
Which is even neater
Ok here's my interpretation of what I think you want.
I am assuming you want to integrate non-promise callbacks with a deferred/promise?
The following example uses the wrapCallback function to wrap two non-promise callbacks, successCallback and errCallback. The non-promise callbacks each return a value, and this value will be used to either resolve or reject the deferred.
I use a random number to determine if the deferred should be resolved or rejected, and it is resolved or rejected with the return value from the non-promise callbacks.
Non angular code:
function printArgs() {
console.log.apply(console, arguments);
}
var printSuccess = printArgs.bind(null, "success");
var printFail = printArgs.bind(null, "fail");
function successCallback() {
console.log("success", this);
return "success-result";
}
function errCallback() {
console.log("err", this);
return "err-result";
}
function wrapCallback(dfd, type, callback, ctx) {
return function () {
var result = callback.apply(ctx || this, arguments);
dfd[type](result);
};
}
Angular code:
var myApp = angular.module('myApp', []);
function MyCtrl($scope, $q) {
var dfd = $q.defer();
var wrappedSuccess = wrapCallback(dfd, "resolve", successCallback);
var wrappedErr = wrapCallback(dfd, "reject", errCallback);
var rnd = Math.random();
var success = (rnd > 0.5);
success ? wrappedSuccess() : wrappedErr();
console.log(rnd, "calling " + (success ? "success" : "err") + " callback");
dfd.promise.then(printSuccess, printFail);
}
Example output where the random number is less than 0.5, and so the deferred was rejected.
err Window /fiddlegrimbo/m2sgu/18/show/
0.11447505658499701 calling err callback
fail err-result

Promise API - combining results of 2 asynchronous call

With promise API, how to send two asynchronous request in parallel, and resolve the combined result as the response.
var get = function(id){
var res1, res2;
var deferred = $q.defer();
Db.get(id, "abc")
.then(function (d) {
//deferred.resolve(d));
res1 = d;
}, function (e) {
//error
});
Db.get(id, "def")
.then(function (d) {
//deferred.resolve(d));
res2 = d;
}, function (e) {
//error
});
//?????? how to return {res1:res1 , res2: res2}
return deferred.promise;
};
now, when I call get() like
get(123).then(function(d)){
// d= {res1: res1, res2: res2}
},
...
I need to get the combined result as indicated. How to do this with Angular promise API?
As #Matt said, you need to use $q.all, but the usage isn't quite right. AngularJS doesn't support .done and .fail and they don't work quite like that anyway in that there's no such thing as a promise for multiple values, instead you just have a promise for an array.
If you were writing this using the full Q we would write:
var get = function (id) {
return Q.all([Db.get(id, "abc"), Db.get(id, "def")])
.spread(function (res1, res2) {
return {res1: res1, res2: res2};
});//the error case is handled automatically
};
In this case, .spread acts like .then except that it spreads the results of an array for a promise out over the arguments of its onFulfilled function. To change this to use the promise methods from AngularJS we just need to make do without .spread. This leads to the following solution:
var get = function (id) {
return $q.all([Db.get(id, "abc"), Db.get(id, "def")])
.then(function (res) {
return {res1: res[0], res2: res[1]};
});//the error case is handled automatically
};
The beauty of this is that we are freed from handling all the nitty grity of error propagation and storing the partial results because .then acts as a filter. If you leave out the error handler, it automatically propagates any errors. This means that if either of the input promises are rejected, the result will be rejected. If both promises are fulfilled successfully, res is the array of those resolution values.
I have something to add to #ForbesLindesay answer.
In our case, we wanted partial results: if a request failed (eg. server has an hiccup, we request something deleted by somebody else, etc.), we still want to collect the valid responses, and to report the errors.
I found out that we need to handle success and failure on each promise, returning a value that will be collected by $q.all.
Here is our code, simplified and made generic ('item'...):
var promiseList = _.map(itemList, function(item)
{
return DataService.getISubtems(item.id)
.then(
function(response)
{
var subItems = response.data;
$log.info('Received sub-item list;' + subItems.length + ';items received');
return subItems;
},
function(reason)
{
$log.warn('Sub-item list not received for item;' + item.name + ';' + item.id);
$scope.errorList.push('Sub-item list not received for item "' + item.name + '"');
}
);
});
$q.all(promiseList)
.then(function(itemArray)
{
// We get an array of arrays interleaved with undefined value when an error was raised.
// That's because error handling doesn't return anything, ie. returns undefined.
// We remove these undefined values then put all operations at the same level.
var allOperations = _(operationArray).reject(_.isUndefined).flatten().value();
if ($scope.errorList.length > 0)
{
NotificationService.warning('Items Fetching', 'Errors while getting item list:\n' +
$scope.errorList.join('\n'));
}
$scope._onItemListReceived(allItems);
});
Note that we use Lodash (_.map, _.flatten, _.reject, _.isUndefined) but I think the usage is quite clear (that's the nice point of this library!).
You can use the angular-q-spread library and then use the same code as #ForbesLindesay's first example:
// The module needs $q-spread as a dependency:
// angular.module('…', ['$q-spread']);
var get = function (id) {
return $q.all([Db.get(id, "abc"), Db.get(id, "def")])
.spread(function (res1, res2) {
return {res1: res1, res2: res2};
});
};

Categories