I'm still learning about Promises and I'm facing a problem. Maybe you can help me.
I have a task which does some checks before running the real stuff. Some of those checks are sync, others are async.
So I'd like to have something like this:
q.all([
jenkins.checkConfig,
gitlab.checkConfig,
sonar.checkConfig
])
.then(
function() {
doSomethingReallyCoolHere();
}
);
But what if sonar.checkConfig is not a promise ? How can I give it a promise behavior ?
Currently I'm doing this
var checkConfig = function() {
var qChecked = q.defer();
var isOK = awesomeSyncTestHere();
if (isOK) {
qChecked.resolve();
}
else {
qChecked.reject();
}
return qChecked.promise;
}
But it looks stupid.
I guess that's not the good way to do it, right ?
Thanks a lot
There is no problem with placing non-promise values in the input array to Q.all. They will just be treated like a promise that had been fulfilled with them.
However, you need to put actual values (or promises) in the array, not the functions that would return them. Just go for
Q.all([
jenkins.checkConfig(),
gitlab.checkConfig(),
sonar.checkConfig()
]).then(doSomethingReallyCoolHere);
If you do want your checks to throw (and prevent the then callback from running), you will need to return a rejected promise indeed. You shouldn't use a deferred for that, though:
function checkConfig() {
if (awesomeSyncTestHere())
return Q(true);
else
return Q.reject(new Error("awesome fail"));
}
or, if you have a test that really throws, just use Q.try
function checkConfig() {
return Q.try(awesomeSyncTestHere);
}
Related
I saw promise implementation in Handling multiple catches in promise chain which produce a very readable chain
return validateInput
.then(checkLoginPermission)
.then(checkDisableUser)
.then(changePassword);
However, in order to do this each function needs to return a value instead of a Promise? Since Promise can resolves to either value or a Promise so this is not a problem. My goal is to turn every function to have readable clear logic as such.
The problem occurs when trying to unwind the nested promise function
return validateInput
.then(function(resultA) {
return checkLoginPermission
.then (function(resultB) {
// Do something with resultA
})
});
Imagine the original implementation involves accessing the value from previous promise. With nested promise, it is easily achievable. But with flatten chain, I would need to break up each function like this
function validateInput = function (resultA ) {
return Promise.resolve({resultA : resultA, resultB :
}
function checkLoginPermission = function (mix ) {
let resultA = mix.resultA;
let resultB = mix.resultB
//Do something with resultA
...
}
This is worse when the last function in the chain rely on something from the very beginning. That means the value have to be passed down from the beginning of the chain even if it was not used.
So am I accidentally stepping on some kind of anti-pattern that might affect performance? How else can I achieve good readability without all these hassles?
This is actually where async and await come in. It's good when you need results across multiple asynchronous calls/promises to be in scope. If you can use that, I'd say try it.
async function foo () {
const input = await validateInput()
const hasPermission = await checkLoginPermission(input)
const result = await checkDisableUser(hasPermission)
return await changePassword(result)
}
Just pass the variables into what function as they need to be. Just showing an example there. I was also a bit unsure of how you're setting validateInput, i think you need to put await infront of the function call itself.
If you cannot use async/await, I usually go with your 2nd code snippet, or define the higher scope variables ontop:
let resultA
return validateInput
.then(function(result) {
resultA = result
return checkLoginPermission
.then (function(resultB) {
// Do something with resultA
})
});
Promises are pattern, related to functional programming, there direct passing data from one function to other is a basic (it's called compose, here examples: http://scott.sauyet.com/Javascript/Talk/Compose/2013-05-22/). So it's not anti-pattern by no means.
I can't see any problem in such pattern. You can pass any data you want to next Promises and in nested Promises grab what they need. It's preety transparent and clear:
function validateInput() {
return Promise.resolve({resultA: 1});
}
function checkLoginPermission(result) {
return new Promise(function(resolve, reject) {
// ...
// code
// ...
result.resultB = 2;
return resolve(result);
});
}
function checkDisableUser(result) {
return new Promise(function(resolve, reject) {
// grab some data from previous function
let resultB = result.resultB;
// ...
// code
// ...
result.resultC = 3;
return resolve(result);
});
}
function changePassword(result) {
return new Promise(function(resolve, reject) {
// grab some data from previous functions
let resultB = result.resultB;
let resultC = result.resultC;
// ...
// code
// ...
result.resultD = resultB * resultC;
return resolve(result);
});
}
validateInput()
.then(checkLoginPermission)
.then(checkDisableUser)
.then(changePassword);
Also you can collect data in some variable, declared before Promises and so you will not have to pass result. But it will destroy functional nature of Promises.
The inner .then(/* ... */) callbacks can return either a primitive value or a Promise that resolves to some value. If it is another promise then the next .then won't start until the inner promise is resolved. Essentially, Promises always resolve to a non-promise type. If you resolve or return another Promise, it will be automatically unwrapped.
I would like to propose a solution using ramda.js#pipeP().
The good thing about this function is that it resolves promises sequentially.
We can rewrite your example using pipeP():
import pipeP from 'ramda/src/pipeP'
pipeP([
checkLoginPermission,
checkDisableUser,
changePassword
])(initialValue)
.then(responseChangePassword => { ... })
The results of a previous promise are passed to the following one.
This is my JS:
self.obj = {}
self.obj.accessErrors = function(data) {
var cerrorMessages = [];
for (prop in data) {
if (data.hasOwnProperty(prop)){
if (data[prop] != null && data[prop].constructor == Object) {
self.obj.fetch[accessErrors](data[prop]);
}
else {
cerrorMessages.push(data[prop]);
}
}
}
return cerrorMessages;
};
self.obj.fetch = {
X: function() {
// do stuff.
},
Y: function(callback) {
$http.get('/yposts/').then(
function(response) {
self.posts = response.data;
callback(self.posts);
},
function(response) {
self.posts = {};
self.obj.accessErrors(response.data).then(function(cerrrorMessages) {
callback(posts, cerrorMessages);
});
}
);
}
};
And I am getting an error pointing to this line:
self.obj.accessErrors(response.data).then(function(cerrrorMessages) {
The error says:
TypeError: self.obj.accessErrors(...).then is not a function
Any idea how to solve this?
self.obj.accessErrors(response.data) does not return a promise so therefore, you can't use promise methods on it.
If you want it to return a promise and you want that promise to reflect when all the fetch() operations are done and those operations are actually async, then you will have to make all your async code into using promises and you will have to combine them all using Promise.all() or the angular equivalent and convert from using callbacks in fetch to just using a promise. Right now, you have a mix which is difficult to program with.
The .then() construction is only needed when using Promise objects - essentially, instead of returning a value, the function returns an object that resolves to a value at some point in the future (which is then passed into the function that you pass to .then().
But you are right in that you need an asynchronous pattern to do this correctly, since fetch.Y is an asynchronous method. A good thing to do would be to create an array of promises at the beginning of your accessErrors function, like so:
var fetchPromises = [];
and then replace self.obj.fetch[accessErrors](data[prop]); with something that calls push on that array to add the promise that fetch returns to it.
Then, instead of returning accessErrors, return Promise.all(fetchPromises).
This will require some fairly significant modification to your code, however - namely, you will need to rewrite it so that it uses the Promise API instead of this callback by itself (which shouldn't be too difficult to do).
I have an array of items that I want to insert into an SQL server. I am using promises for this and in order to execute each insert sequentially I wrote the following method:
var ForeachPromise = function (array, func) {
var promise = func(array[0]);
for (var i=1; i < array.length; i++) {
promise = promise.then(function() { return func(array[i]) });
}
return promise;
}
The idea is that when func is call it will return a promise, which will then be chained to the previous promise.
...
return ForeachPromise(type.subprocessen, function(subproces) {
return newSubproces(subproces, typeId, dienstId, createData, s + 1);
});
I haven't actually tested it yet, but I assume that something like this will work. My question however is am I using promises correctly? Promises are great but easily misunderstood and I just want to be sure that I'm not making any fundamental mistakes.
Yes, that approach is fine, and works well with promises. Two minor quibbles:
you should take care for the case of an empty array. Start your chain with Promise.resolve() (a promise fulfilled with undefined), and begin your loop at index 0.
As the then callback is asynchronous, your i variable has the wrong value - the classical closure in a loop fallacy.
Using the .reduce method does help with both problems:
function foreachPromise(array, func) {
return array.reduce(function(promise, elem, i) {
return promise.then(function() { return func(elem) });
}, Promise.resolve());
}
This question already has answers here:
How do I access previous promise results in a .then() chain?
(17 answers)
Closed 8 years ago.
From what I understand one of the main selling points for Promises is the ability to write flat code (or, flatter than callback hell).
Though it seems that in many cases we need to nest promises, in order to use closure. For example (from q's docs, though I use Bluebird):
function authenticate() {
return getUsername()
.then(function (username) {
return getUser(username);
})
// chained because we will not need the user name in the next event
.then(function (user) {
return getPassword()
// nested because we need both user and password next
.then(function (password) {
if (user.passwordHash !== hash(password)) {
throw new Error("Can't authenticate");
}
});
});
}
Is there a cleaner way to do this, without nesting?
EDIT: I've managed to clean up this specific example using .all, but there are more complex cases where I don't think it can be done:
function authenticate() {
return Promise.all([
getUsername().then(getUser),
getPassword()
]).spread(function (user, password) {
if (user.passwordHash !== hash(password)) {
throw new Error('Can\'t authenticate');
}
});
}
Yes, you can always flatten a promise chain with Promise.all (shorthand through Promise.join in Bluebird) by using promises for the proxies they are. After all - promises abstract values, you can always unwrap a promise as late as you want and have other variables depend on it.
Whether or not it's more readable is another debate:
foo().then(function(a){
return bar(a).then(function(b){
return g(a,b); // "needed" to nest because I wanted `a`
});
});
Can be written as:
var a = foo();
var b = a.then(bar);
Promise.join(a, b, function(a,b){
return g(a, b); // alternatively, res could have been `Promise.join(a,b, g)`
});
So generally - you can always avoid nesting but a lot of time you might not want to.
In your case, this can even be:
function authenticate() {
var userPass = Promise.all([ getUsername().then(getUser), getPassword()]);
var passHash = userPass.get(0).get("passwordHash");
var newHash = userPass.get(1).then(hash);
var equal = Promise.join(userHash, newHash, function(a, b){ return a !==b });
return equal.then(function(val){ if(!val) throw new Error("..."); });
}
Flatter? Sure. Better? That's a whole other question. If you have a nested for loop, you might want to keep it a nested for loop and nest rather than hack around that option and use a single loop.
A user is able to make asynchronous calls by entering a value in a UI.
When the user changes the value in the UI, another async call is made - possibly before the callback supplied to the promise returned from the async call has been invoked.
The async API being used returns a Q based promise.
How can I cancel the original call gracefully, ensuring that even if the system returns with a value for the first call, the .then part of the promise is not invoked with that value (but that it is eventually invoked when the second async call completes)?
Back in the day I had a case like this (node.js) where I was either doing a web or a db request. I had a timeout object, which I think was cancellable (sry, don't remember the details) and had promises on both of them. So if the webpage returned first, I'd cancel the timeout and return the html. And if the timeout happened first, I updated a "timedout" field in some JSON to yes, so that if the web call ever returned, it would know just to die. It was a little mind-blowing, because with these promises, I could enter the function once, and actually return twice!
Hope that helps
-Tom
Absent some cancellation API, each async call will run to completion, but if all you're looking for is canceling the .then() functions, then that's doable. The most straight-forward way is a counter:
var counter = 0;
function onClick() {
counter++;
doAsyncCall().then(function(result) {
if (!--counter) {
myFunc(result);
}
});
}
But you asked for graceful, so maybe a more reusable construct would be something like this:
var idle;
function onClick(){
idle = Overtake(idle, doAsyncCall());
idle.then(myFunc).then(myOtherFunc);
}
which could be implemented like this:
function Overtaker(prior, callp) {
if (prior) {
prior.cancel();
}
var p;
p = new Promise(function(resolve, reject) {
callp.then(function(value) { return p && resolve(value); },
function(reason) { return p && reject(reason); });
});
this.cancel = function() { p = null; }
this.then = function(onFulfilled, onRejected) {
return p.then(onFulfilled, onRejected);
}
}
function Overtake(prior, p) {
return new Overtaker(prior, p);
}
I'm using ES6 promises, but Q promises seem similar, so hopefully this works there as well.