Can you avoid nesting altogether with Promises? [duplicate] - javascript

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.

Related

why the variable is undefined

I could access the variable 'savedCards ' from the first promise, it has some value. Inside the second promise it's undefined but the variable 'eCard' has value. Please explain me why ?
saveCard(eCard: IEcards) {
var savedCards: IEcards[] = [];
this.storage.get("key").then((value) => {
if (value.saves == undefined) {
var saves = savedCards;
value.saves = saves;
}
savedCards = value.saveCard; // have value and can be accessed
console.log(savedCards);
}).then((data) => {
console.log(savedCards); // savedCards is undefined but eCard.id has value
this.globalProvider.IsCardExist(eCard.id, savedCards).then((data) => {
if (!data.response) {
this.globalProvider.AddEcardToStorage("saves", eCard);
}
});
});
}
When you need to access the intermediate values in your chain, you should split your chain apart in those single pieces that you need. Instead of attaching one callback and somehow trying to use its parameter multiple times, attach multiple callbacks to the same promise - wherever you need the result value.
function getExample() {
var a = promiseA(…);
var b = a.then(function(resultA) {
// some processing
return promiseB(…);
});
return Promise.all([a, b]).then(function([resultA, resultB]) {
// more processing
return // something using both resultA and resultB
});
}
[Edit]
You want to know why, and here is the answer: ES6 came with generator functions, which allow to break the execution apart in pieces at arbitrarily placed yield keywords. Those slices can be run after each other, independently, even asynchronously - and that's just what we do when we want to wait for a promise resolution before running the next step.
Your code is resolving the second promise before the first one. You can not assure that your code will work as you want by using "then()". If you want a synchronous resolution, you should go for another way.
[Edit 2]
Try to use await and see if you are able to solve your problem. More info here: http://2ality.com/2017/08/promise-callback-data-flow.html

Flattening promise chain with readable function name

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.

Using forEach to sequentially execute functions in Q

I'm attempting to run a series of functions based upon the Q API using their first strategy for sequences. This suggests the pattern:
var funcs = [foo, bar, baz, qux];
var result = Q(initialVal);
funcs.forEach(function (f) {
result = result.then(f);
});
return result;
What structure are each of the functions within the array meant to take? I am quite confused about when to use the return def.promise;. Is that simply always the last line? Will it frequently or always immediately follow def.resolve(someVar). Is something like this then ended structure?
function foo(f){
var def = Q.defer();
f++;
def.resolve(f);
return def.promise;
}
So that each subsequent function within the array will receive the newly calculated value of f: in this case, if var initialVal = 1; and four functions each incrementing f++, the returned result will be 4? How do I access that returned value? console.log(result) prints { state: 'pending' } .
What structure are each of the functions within the array meant to take?
Q.js allows promises to be created in several ways. For example :
function foo(value) {
var def = Q.defer();
def.resolve(value + 1);
return def.promise;
}
function foo(value) {
return Q(value + 1);
}
function foo(value) {
return Q.Promise(function(resolve, reject) {
resolve(value + 1);
});
}
Other Promise libs are similar, but not necessarily so flexible. Native js Promises must be constructed with the third of these approaches.
However, in the real world you will only rarely need to create your own Promise. You will typically be dealing with promise-returning lib methods that someone else has written. For example :
function foo(value) {
return lib.doSomethingAsync(value, and, other, params);
}
How do I access that returned value?
The code is easier to understand if member name "result" is replaced with "promise", and result.then(f) is rewritten with an anonymous function that calls f().
function performAsyncSequence() {
var promise = Q(initialVal);
funcs.forEach(function (f) {
promise = promise.then(function(previousResult) {
return f(previousResult);
});
});
return promise;
}
This is 100% equivalent to the code in the question, but now it should be clearer how the previous result is passed down the promise chain.
Accessing all previous promise results in the sequence is more complicated. The answers here discuss the subject comprehensively.

call async function for each array element of a promise result object, make changes and return this object

A Bluebird Promise returns an object that contains two arrays of objects, cars and contracts. Then, I want to iterate over the cars, call an asynchronous function and, based on the returned value, make some changes to the second array and return the initial result object with these changes. I can't figure out how to do this with promises. Or with async, for that matter. I feel like they should be nested promises, but i can;t get it to work at all.
the version with promises:
somePromise().then(function (result) {
Promise.each(result.cars, function (car) {
makeAsyncCall(car.id, function (err, resultArray) {
if (err) {
throw new Error();
}
result.contracts.forEach(function (contract) {
if (resultArray.indexOf(contract.id) > -1) {
contract.car = car.id;
}
});
});
}).then(function (eachResult) {
//eachResult is result.firstArray, which is not interesting.
return result;
});
}).then(function (result)) {
//this gets called before my promise.each gets executed??
}
Can anyone give me a hint as to where my mistake is?
Have a look at my rules of thumb for promise development. The two specific points that apply to your code are:
promisify your async callback-taking functions before using them, specifically
var makeCall = Promise.promisify(makeAsyncCall);
always return promises from your functions that do asynchronous things. This is especially true for callbacks, like the function() { Promise.each(…).then(…) } and the function() { makeAsyncCall(…) }.
With those, you should get to the following:
somePromise().then(function(result) {
return Promise.each(result.cars, function(car) {
return makeCall(car.id).then(function(resultArray) {
// a lookup structure of contracts by id could make this more efficient
result.contracts.forEach(function (contract) {
if (resultArray.indexOf(contract.id) > -1)
contract.car = car.id;
});
});
}).return(result);
}).…

Promises: q.all() with mixed sync and async functions

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);
}

Categories