Bluebird Promise: Nested or conditional chains - javascript

I use Bluebird Promises for a Node.js application. How can I introduce conditional chain branches for my application? Example:
exports.SomeMethod = function(req, res) {
library1.step1(param)
.then(function(response) {
//foo
library2.step2(param)
.then(function(response2) { //-> value of response2 decides over a series of subsequent actions
if (response2 == "option1") {
//enter nested promise chain here?
//do().then().then() ...
}
if (response2 == "option2") {
//enter different nested promise chain here?
//do().then().then() ...
}
[...]
}).catch(function(e) {
//foo
});
});
};
Apart from not having figured out a working version of this yet, this solution feels (and looks) weird somehow. I got a sneaking suspicion that I am somewhat violating the concept of promises or something like that. Any other suggestions how to introduce this kind of conditional branching (each featuring not one but many subsequent steps)?

Yes, you can do it, just like that. The important thing is just to always return a promise from your (callback) functions.
exports.SomeMethod = function(req, res) {
return library1.step1(param)
// ^^^^^^
.then(function(response) {
… foo
return library2.step2(param)
// ^^^^^^
.then(function(response2) {
if (response2 == "option1") {
// enter nested promise chain here!
return do().then(…).then(…)
// ^^^^^^
} else if (response2 == "option2") {
// enter different nested promise chain here!
return do().then(…).then(…)
// ^^^^^^
}
}).catch(function(e) {
// catches error from step2() and from either conditional nested chain
…
});
}); // resolves with a promise for the result of either chain or from the handled error
};

Just return additional promises from within your .then() handler like I show below. The key is to return a promise from within a .then() handler and that automatically chains it into the existing promises.
exports.SomeMethod = function(req, res) {
library1.step1(param)
.then(function(response) {
//foo
library2.step2(param)
.then(function(response2) { //-> value of response2 decides over a series of subsequent actions
if (response2 == "option1") {
// return additional promise to insert it into the chain
return do().then(...).then(...);
} else if (response2 == "option2") {
// return additional promise to insert it into the chain
return do2().then(...).then(...);
}
[...]
}).catch(function(e) {
//foo
});
});
};

Related

Java Script Promises - If Statement Return Best Practices

I'm writing a node.js function that returns a different promise depending on a condition, the cod:
if(condition){
return promise.then(() => {
return Promise.resolve(value)
})
}else{
return anotherPromise
}
Now the problem is that if the condition is true, I need to something after the promise is fulfilled, but in the other case I just return the promise, so the eslint tells me that it's a bad practice to nest promises. So this code won't work for me:
(() => {
if(condition){
return promise
}
}else{
return anotherPromise
}
}).then(() => {
return Promise.resolve(value)
})
Because using this code the then callback will be executed in the two cases.
What is the best practice to handle this case?
If you use classic (ES6 / ES2015+) Promise syntax you have to chain promises (nothing bad with it!).
But you have also option to split the code into functions to gain readability and avoid nesting issues:
const firstCase = () => ... // returning a promise
const secondCase = () => ... // returning a promise
if (condition) {
return firstCase()
} else {
return secondCase()
}
But with ES7/ES2016+ you can use async/await syntax:
// in a "async" function
async function main() {
if (condition) {
await promise // if you need the promise result, you can assign it
return value // the result of an async function is always a Promise.
} else {
return anotherPromise
}
}
or mix both solutions.
A simple suggestion, (this should work) pass the condition in the resolve's argument and check it in the then block. The pseudo-code below will clarify it better:
(() => {
if(condition){
return new Promise((resolve,reject)=>{
//Some tasks
resolve(condition)
//Some reject condition
reject()
})
}
else {
return new Promise((resolve,reject)=>{
//Some tasks
resolve(condition)
//Some reject condition
reject()
})
}
}).then((condition) => {
if(condition) {
//Do something
}
else {
//Do Nothing
}
})
eslint tells me that it's a bad practice to nest promises.
Just tell it to shut *** up disable the linter on this statement. Promises have the ability to be nested precisely so that you can nest them when you need it, and this is one of those cases. Your code is fine.
Seems like you are over complicating it. The then method already returns a promise so you don’t need to put a Promise.resolve inside it.
You can for simplicity do
return condition
? promise.then(() => value)
: anotherPromise

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.

Possible Async Call in a Conditional

I'm refactoring some legacy code that I didn't originally write and I've come upon an issue with asynchronous data loading. The first time a particular modal is opened, a bunch of data representing a form object gets loaded. A function then cycles through the inputs of the form and fleshes them out as needed. It looks something like this (extremely simplified):
component.inputs.forEach(function(input) {
if (input.field == 'foo') {
input.cols = 5;
//etc.
}
if (input.field == 'bar') {
DataService.getBars().then(function(data){
data.forEach(function(e){
input.options.push(e.description);
});
};
}
if (input.field == 'baz') {
input.pattern = /regex/;
//etc.
}
});
return component;
The problem, of course, is that if my input.field is 'bar', the code continues running and hits the final return before the async call to DataService is resolved, so the first time the modal is opened, the input.options have not been filled out for 'bar' input.
Is it possible to make the code wait for the promise from the DataService to be resolved before continuing, or is there another way to handle the situation where in most cases the function is synchronous, but has to make an async call in only one case? Or have I shot myself in the foot by including an async call in this big chain of ifs?
One approach is to create a promise and attach it as a property to your returned object.
function getComponent() {
component.inputs.forEach(function(input) {
//create initial promise
var $promise = $q.when(input);
if (input.field == 'foo') {
input.cols = 5;
//etc.
}
if (input.field == 'bar') {
//chain from initial promise
$promise = $promise.then(function () {
//return promise for chaining
return getBarPromise(input);
});
}
//attach promise to input object
input.$promise = $promise;
});
var promises = [];
angular.forEach(inputs, function(input) {
promises.push(input.$promise);
});
//create composite promise
var $promise = $q.all(promises);
//final chain
$promise = $promise.then( function() {
//return component for chaining
return component;
});
//attach promise to component
component.$promise = $promise;
return component;
};
The returned component object will eventually be filled in with the results of the service calls. Functions that need to wait for completion of all the service calls can chain from the attached $promise property.
$scope.component = getComponent();
$scope.component.$promise.then( function (resolvedComponent) {
//open modal
}).catch( function(errorResponse) {
//log error response
});
Because calling the then method of a promise returns a new derived promise, it is easily possible to create a chain of promises. It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.1
If you want to stay with your existing code structure and make this work, you probably will need to use promises. You can also use javascript's map function. Note: you would need to inject $q into wherever you want to call this function.
function getComponent() {
var deferred = $q.defer(),
deferred2 = $q.defer(),
promises = component.inputs.map(function(input)) {
if (input.field == 'foo') {
input.cols = 5;
deferred2.resolve();
}
else if (input.field == 'bar') {
DataService.getBars().then(function(data) {
data.forEach(function(e){
input.options.push(e.description);
});
deferred2.resolve();
}).catch(function(err)) {
deferred2.reject(err);
});
}
else if (input.field == 'baz') {
input.pattern = /regex/;
deferred2.resolve();
}
return deferred2.promise;
});
$q.all(promises)
.then(function() {
deferred.resolve(component);
}).catch(function(err) {
deferred.reject(err);
});
return deferred.promise;
}
Once each input in component.inputs has been parsed appropriately, then the $q.all block will trigger and you can return your new component object.
Finally, to set your component object, simply do the following:
getComponent().then(function(result)) {
//Set component object here with result
$scope.component = result;
}).catch(function(err) {
// Handle error here
});

Is there any way to chain promises while handling errors as if promises were nested?

I have the following code (http://jsfiddle.net/tj1eo86s/):
promise(1).then(function() {
console.log("OK1");
promise(2).then(function() {
console.log("OK2");
promise(3).then(function() {
console.log("OK3");
}, function() {
console.log("KO3");
});
}, function() {
console.log("KO2");
});
}, function() {
console.log("KO1");
});
If promise(2) is rejected, the output will be:
OK1
KO2
How can I obtain the same behavior while chaining the promises?
I tried with (http://jsfiddle.net/7goh0ds9/):
promise(1)
.then(function() {
console.log("OK1");
return promise(2);
}, function() {
console.log("KO1");
})
.then(function() {
console.log("OK2");
return promise(3);
}, function() {
console.log("KO2");
})
.then(function() {
console.log("OK3");
}, function() {
console.log("KO3");
});
But the output is:
OK1
KO2
OK3
Then I tried to throw an error in the error callbacks (http://jsfiddle.net/cyx6mohy/):
promise(1)
.then(function() {
console.log("OK1");
return promise(2);
}, function() {
console.log("KO1");
throw "KO1";
})
.then(function() {
console.log("OK2");
return promise(3);
}, function() {
console.log("KO2");
throw "KO2";
})
.then(function() {
console.log("OK3");
}, function() {
console.log("KO3");
throw "KO3";
});
But now I have:
OK1
KO2
KO3
I actually understand all those behaviors, but I have not been able to find a solution to handle errors as if promises were nested.
If your question is how to I generically make chaining handle errors the exact same way as arbitrary nesting, the answer is that you can't in a generic way. Chaining is a fundamentally different control structure than arbitrary nesting.
You could design a nesting that had fundamentally the same behavior as chaining. Or, if you had a specific error handling behavior from a nested structure that you wanted to emulate in a chained world, you "might" be able to use the appropriate error handling and control flow in a chained sequence to make that happen. Note, I say "might" because it depends entirely upon what the nested sequence was doing. You also might not be able to emulate it with pure chaining.
If your question is how do I make this sequence of chained promises generate this specific response when this specific promise in the chain rejects, then that might be possible, but it would be coded for your specific circumstance and desired outcome, not in some generic way.
Basically, there are reasons to use chaining and there are reasons to use nesting and reasons to use a combination of the two. Like you, I prefer chaining because the control flow and readability just seems simpler, so I always start out with that structure in mind, but there are definitely times in my code where I have to use nesting for some part of the operation (often because of branching decisions or certain types of required error handling).
You can reject with the index as argument, then add a .catch and handle the error there:
var breakOnPromise = 2;
function promise(i) {
return new Promise(function(resolve, reject) {
if (i == breakOnPromise) {
reject(i); // pass index to catch
} else {
resolve();
}
});
}
promise(1).then(function() {
console.log("OK1");
return promise(2);
}).then(function() {
console.log("OK2");
return promise(3);
}).then(function() {
console.log("OK3");
}).catch(function(i) {
// This is just for your case,
// but you could use an `if` statement
var err = ["KO1", "KO2", "KO3"][i - 1];
console.log(err);
});

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

Categories