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
Related
I was trying to implement promise from scratch.
Question:
I wasn't sure how do I implement Finally? (I'm guessing finally will
execute after then's and the catch is invoked. (ONLY once))
If you feel any refactors can be made to my code, please feel free to suggest. This is my naive attempt to implement promises.
This is my implementation:
function Promisify(fn) {
let status = 0; // 0 = unfulfilled, 1 = resolved, 2 = rejected
let result;
let error;
let thenFns = [];
let catchFns = [];
let finallyFn = undefined;
// Public Methods.
this.then = function(fn) {
thenFns.push(fn);
doThen();
return this; // for chaining
};
this.catch = function(fn) {
catchFns.push(fn);
doCatch();
return this; // for chaining
};
// TODO: Implement finally
this.finally = function(fn) {
finallyFn = fn;
// dofinally(fn);
return this;
}
// Private Methods
function resolve(r) {
if (status) throw Error('can not resolve, already handled');
status = 1;
result = r;
doThen();
}
function reject(e) {
if (status) throw Error('can not reject, already handled');
status = 2;
error = e;
doCatch();
}
function doThen() {
if (status === 1) {
while(thenFns.length) {
thenFns.shift()(result);
}
}
}
function doCatch() {
if (status === 2) {
if (catchFns.length === 0) {
console.error('uncaught error')
}
while(catchFns.length) {
catchFns.shift()(error);
}
}
}
try {
fn(resolve, reject);
} catch (e) {
reject(e);
}
}
// ======== QUESTION: Caller ================
const demoFail = new Promisify((resolve, reject) => {
setTimeout(function() {
reject('Howdy! from DemoFail')
}, 1000);
});
demoFail
.then(val => console.log("DemoFail Then!!"))
.catch(console.error) //Should throw an error.
.then(val => console.log("Second then!"))
.catch(
(err) => {
throw new Error('error', err);
})
.finally(val => console.log("Executed Finally"))
Here are some things missing from your implementation:
.then() needs to return a new promise, not the same promise.
The return value of a .then() handlers (if it's not a promise) becomes the resolved value of the newly returned promise returned in step #1.
If the return value of a .then() handler is a promise, then the newly returned promise from step 1 doesn't resolve until the promise returned from the .then() handler resolves or rejects with that value.
When calling a .then() handler, you need try/catch around the call to the handler because if the handler throws, then that turns the promise returned into step #1 into a rejected promise with the throw error as the reject reason.
There is similar logic for all the .catch() handlers (it also returns a new promise and the return value of the handler affects the newly returned promise from step #1).
When you store .then() or .catch() callback fns, you also need to store the newly create promise that you returned separate for each callback because that promise will be affected by the return value or exception thrown in the callback.
The spec itself that covers this is fairly simple. You can read it here. And, here's an implementation of that spec.
Your simple version looks like it probably works for one level of promise like fn().then(), but it doesn't do proper chaining and doesn't catch exceptions in handlers and doesn't pay attention to return values in handlers which are all pretty fundamental behaviors of promises. Unfortunately, there is no super simple way to write promises that includes the fundamental behaviors. It just takes more code to support them.
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
I am trying to use an Angular service to make a call to either use fs.readFile or fs.writeFile depending on type of button pressed in order to understand how node and angular promises interact. What I have is reading writing files, but does not send back read data, nor does it throw any errors for me to understand what has gone wrong.
//HTML
<button ng-click="rw('write')">WRITE FILE</button>
<button ng-click="rw('read')">READ FILE</button>
//angular
angular.module('test', [])
.controller('ctrl', function($scope, RWService){
$scope.rw = function(type){
RWService.rw(type)
.then(
function(res){
console.log('success');
},
function(err){
console.log('error');
})
};
})
.service('RWService',['$http', '$q', function($http, $q){
this.rw = function(type){
var promise = $http.get('./rw/' + type);
var dfd = $q.defer();
promise.then(
function(successResponse){
dfd.resolve(successResponse);
},
function(errorResponse){
dfd.reject(errorResponse);
}
);
return dfd.promise;
};
}]);
//node
var fs = require('fs')
, async = require('async')
, Q = require('Q');
var dest = './file.txt';
var rw = {
write: function(data){
data = data.repeat(5);
return Q.nfcall(fs.writeFile, dest, data);
}
, read: function(data){
data = data.repeat(5);
var deferred = Q.defer();
console.log('inside read');
fs.readFile(dest, 'utf8', function(err, data){
if (err){
deferred.reject('some error');
}else{
deferred.resolve(data);
}
});
return deferred.promise;
}
};
module.exports = exports = rw;
//node server
app.get('/rw/:type', function(req, res, next){
var type = req.params.type;
var data = 'some text string\n';
if (type == 'write'){
//omitted fro brevity
}else{
rw.read(data)
.then(function(response){
return {'response': response};
})
.catch(function(err){
return {'index.js error': err};
});
}
});
I structured the angular $q portion off of this blog post.
Here is a native Promise implementation of your code.
var fs = require('fs');
var dest = './file.txt';
var rw = {
write: function(data){
return new Promise(function (resolve, reject) {
data = data.repeat(5);
fs.writeFile(function (err, result) {
if (err) return reject(err.message);
return resolve(result);
});
});
},
read: function(data){
return new Promise(function (resolve, reject) {
data = data.repeat(5);
fs.readFile(dest, 'utf8', function(err, contents) {
if (err) return reject(err.message);
return resolve(contents.toString());
});
});
}
};
module.exports = exports = rw;
[edit: I just changed the code to put data=data.repeat(5) inside the promise factory method. Basically, if anything CAN raise an exception, you should try to put it inside that promise function, or you again run the risk of silently killing the script again.]
A couple of comments:
Returning deferred is incredibly useful, but you have to be careful about how you use it. I personally only use it if the asynchronous code cannot be wrapped in a simple function (such as a class instance that creates a promise in its constructor and resolves/rejects in different child methods). In your case, probably what is happening is that the script is failing in a way that fs.readFile() never gets called -- and so deferred.resolve() and deferred.reject() will never be reached. In cases like this, you need to use try/catch and always call deferred.reject() in there as well. It is a lot of extra work that is easily avoided.
Instead, you should try to use the vanilla standard implementation of Promises as you see above.
Lastly, Q was a groundbreaking library that basically taught the world how to do promises in the first place, but it has not been updated in years and was never particularly feature-rich or fast. If you need more features, take a look at when.js*, kew or Bluebird (note that Bluebird claims to be the fastest, but I've personally found that to be untrue.)
(*I actually loved working with when.js and find it a bit painful using dumb native promises, but hey, standards are standards.)
[edit: Adding details on the Angular side of things]
So based on your comment, here is what I suspect you are also looking for. You will see that here I am using $http.get() as the only promise. No need to use defer() once you are inside a promise, so actually there is no need to even include $q.
I'm sorry, I've never used service(). Even Angular's own documentation on creating services uses the factory() method, so that's what I'm using here.
.factory('RWService',['$http', function($http){
return {
rw: function (type) {
// $http returns a promise. No need to create a new one.
return $http.get('./rw/' + type)
.then(function (response) {
// You can do other stuff here. Here, I am returning the
// contents of the response. You could do other stuff as
// well. But you could also just omit this `then()` and
// it would be the same as returning just the response.
return response.data;
})
.catch(function (err) {
// You can do other stuff here to handle the error.
// Here I am rethrowing the error, which is exactly the
// same as not having a catch() statement at all.
throw err;
});
}
};
}]);
If you read the comments in the code above, you should realize that you can write the same code like this:
.factory('RWService',['$http', function($http){
return {
rw: function (type) {
return $http.get('./rw/' + type);
}
};
});
The only difference here is that RWService.rw() will eventually resolve the entire response object rather than the response data.
The thing to keep in mind here is that you can (and absolutely should) try to recycle your promises as much as possible. Basically, all you need to know about promises is:
every promise has a then and catch method you can wrap your logic in;
every then and catch return as a new promise;
if you throw an exception inside any then or catch, you will be thrown straight to the next catch, if there is one;
if you return a value from any then or catch, it will be passed as the argument to the very next then in the chain;
when the chain runs out of thens or you throw an exception and there are no more catches, the promise chain ends; and
then and catch are fast, but they are still asynchronous, so don't add new elements to a promise chain if you don't genuinely need them.
The following code gets stuck:
var Promise = require('promise');
var testPromise = function(){
return new Promise(function(fulfill, reject){
element.all(by.repeater('item in menu.items')).first().then(function(el){
console.log('test f');
fulfill(el);
console.log('test fe');
});
});
};
... called by the following:
testPromise().then(function(el){
console.log('test successful '+el);
});
The console prints
test f
test fe
And get stuck no more code is executed. It never reaches the then although fulfill has been called.
if using nested promises is an anti pattern then how do I do the following without a nested promise:
var getMenuItemEl = function(itemName){
return new Promise(function(fulfill, reject){
var elFound;
element.all(by.repeater('item in menu.items')).then(function(els){
async.each(els, function(el, callback){
el.getText().then(function(text){
console.log('getMenuItemEl:'+text);
if(text === itemName){
elFound = el;
}
callback();
});
}, function(err){
console.log('complete '+elFound);
if(elFound){
console.log('fulfill');
fulfill(elFound);
console.log('after fulfill');
}else{
reject('no item found');
}
});
});
});
};
This also gets stuck after the fulfill has been called
if using nested promises is an anti pattern then how do I do the following without a nested promise
You would not use the async library. Since all your methods (element.all(), el.getText() etc) do already return promises, you don't need to go back to node-style error callbacks. From my "rules for promises",
every async function (even if it is a callback) should return a promise, and use your libraries' methods to compose them. You really don't need to call the Promise constructor on your own. That iteration you are doing there can be easily done by a map over the els, then collecting all the single promises together with Promise.all.
function getMenuItemEl(itemName) {
return element.all(by.repeater('item in menu.items'))
.then(function(els){
return Promise.all(els.map(function(el) {
return el.getText()
.then(function(text){
console.log('getMenuItemEl:'+text);
return (text === itemName) ? el : null;
});
}));
})
.then(function(foundEls) {
for (var i=0; i<foundEls.length; i++)
if (foundEls[i] != null) {
console.log('fulfill');
return foundEls[i];
}
throw new Error('no item found');
});
}
The following code solves my problem, mainly due to my lack of knowledge of the protractor api
var doClickMenuItemEl = function(itemName){
return element.all(by.repeater('item in menu.items')).filter(function(elem, index) {
return elem.getText().then(function(text) {
return text === itemName;
});
}).then(function(els){
els[0].click();
});
};
It also says on https://github.com/angular/protractor/issues/379 that
Yes, this is inherited from webdriver promises, which are not quite promise A+ compliant, unfortunately.
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