How to use if expression in Promise.all()? - javascript

I want to use Promise.all() to handle two promise object, but the second is inner a if expression. How to handle this situation?
It looks like this:
functionA();
if(true) {
functionB();
}
functionA() and functionB() both return a promise object. In normal case, I could use
Promise.all([
functionA(),
functionB()
]).then(resule => {
console.log(result[0]); // result of functionA
console.log(result[1]); // result of functionB
})
But how to handle with the if expression? Should I wrap the if(true){functionB()} in a new Promise()?

Well, you can use ifs if you use promises as proxies for values, or you can nest the promise one level - personally - I prefer using them as the proxies they are. Allow me to explain:
var p1 = functionA();
var p2 = condition ? functionB() : Promise.resolve(); // or empty promise
Promise.all([p1, p2]).then(results => {
// access results here, p2 is undefined if the condition did not hold
});
Or similarly:
var p1 = functionA();
var p2 = condition ? Promise.all([p1, functionB()]) : p1;
p2.then(results => {
// either array with both results or just p1's result.
});
Wrapping the conditional in a new Promise is explicit construction and should be avoided.

Promise.all([ cond==true ? p1 : '', p2])

In my case i had multiple conditions
functionA();
if(condition1) {
functionX();
}else if (condition2) {
functionY();
}....
so i did the following
const promises = [];
promises.push(functionA());
if (condition1) promises.push(functionX());
else if (condition2) promises.push(functionY());
......
Promise.all(promises).then((results) => console.log('results', results) );

Related

Why does the function getSafestCountriesNames() return promise{pending} when i call it and when i use async/await it returns undefined?

This is the function:
export const getSafestCountriesNames = async() => {
try{
const names = await getCountriesData();
names.forEach((country) => {
const {score} = country.advisory;
if(score == 0){
const liEl = createDOMElement('li', {className: 'name'});
liEl.innerHTML = `${country.name}`;
return liEl;
}
});
}
// …
}
When I am calling it in here I get undefined:
async function func(){
const names = await getSafestCountriesNames();
console.log(names);
}
func();
It returns Promise{pending} because when a function has the async keyword, it has to return a Promise. See here.
Nonetheless there's a deeper issue in your code. Your return statement is inside the callback of the forEach. That's the return statement of the callback, not the one of getSafestCountriesNames.
Since you need to return a Promise (for you're using await), you can either wrap your logic inside something like:
return new Promise((resolve, reject) => {
// your logic here, resolving or rejecting instead of just returning
})
or keeping things simple: return Promise.resolve("something")
When you call the function with await, it resolves undefined because you're not resolving anything in your Promise.

Conditionally chaining promises inside and outside of a loop in javascript

In the following code, when the condition within the loop is satisfied we can't really be sure in which order are both asyncFunction called.
siblings.forEach(function(sibling, index) {
// This condition may only be satisfied once (only one sibling may have data-selected true)
if (sibling.getAttribute('data-selected') == 'true') {
asyncFunction(sibling);
}
})
asyncFunction(element);
Since I know asyncFunction() returns a promise I could chain those to ensure the order:
asyncFunction(sibling).then(asyncFunction(element));
But how to do this taking the condition into account ?
I considered wrapping the loop in a promise that resolves when the condition is satisfied or after the loop ends, but it seems a tad convoluted.
// Untested
function checkSiblings(siblings) {
let promise = new Promise(function(resolve, reject) {
siblings.forEach(function(sibling, index) {
if (sibling.getAttribute('data-selected') == 'true') {
resolve(asyncFunction(sibling));
}
})
resolve();
})
}
checkSiblings(siblings).then(asyncFunction(element));
I am sure this has been addressed before, but I can't find the right search keyboards.
Thank you
If only one of the siblings can meet the criteria, using find will simplify your code:
const selected = [...siblings].find( elem => elem.getAttribute('data-selected') == 'true' );
Mark checkSiblings as async so it always returns a Promise, and have it return the asyncFunction Promise when appropriate:
return selected ? asyncFunction(selected) : null;
Then you can chain them as before:
checkSiblings(siblings).then(() => asyncFunction(element));
All together you’d have something like this:
async function checkSiblings (siblings) {
const selected = [...siblings].find( elem => elem.getAttribute('data-selected') == 'true' );
return selected ? asyncFunction(selected) : null
}
checkSiblings(siblings).then(() => asyncFunction(element));
There are several ways. One of them is using Array.reduce to resolve promises sequentially:
[...siblings].reduce(
async (prev, sibling)) => {
// Await initial/previous promise (discard results)
await prev;
// This condition may only be satisfied once
// (only one sibling may have data-selected true)
if (sibling.getAttribute('data-selected') == 'true') {
// Return the async function promise
return asyncFunction(sibling);
}
// async functions always returns a promise.
// No need to return anything
// Initial promise autoresolved
}, Promise.resolve())
// Once all have been executed
.then( () => asyncFunction(element));

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.

How do I chain multiple conditional promises?

In my code I have conditional tasks, which all return a promise. I need the tasks to run in sequence.
My current implementation looks something like this:
var chain = [];
if (/* some condition for task A */) {
chain.push(function(doContinue){
taskA().then(doContinue);
});
}
if (/* some condition for task B */) {
chain.push(function(doContinue){
taskB().then(doContinue);
});
}
if (/* some condition for task C */) {
chain.push(function(doContinue){
taskC().then(doContinue);
});
}
var processChain = function () {
if (chain.length) {
chain.shift()(processChain);
} else {
console.log("all tasks done");
}
};
processChain();
This works fine, but initially I was looking for a way to create the chain using only Promises and chaining all functions using .then, but I wasn't able to get a working solution.
If there's a cleaner way using only Promises and chains of then calls, then I'd love to see an example.
One possible approach:
var promiseChain = Promise.resolve();
if (shouldAddA) promiseChain = promiseChain.then(taskA);
if (shouldAddB) promiseChain = promiseChain.then(taskB);
if (shouldAddC) promiseChain = promiseChain.then(taskC);
return promiseChain;
Another one:
return Promise.resolve()
.then(shouldAddA && taskA)
.then(shouldAddB && taskB)
.then(shouldAddC && taskC);
You can use the new async/await syntax
async function foo () {
let a = await taskA()
if (a > 5) return a // some condition, value
let b = await taskB()
if (b === 0) return [a,b] // some condition, value
let c = await taskC()
if (c < 0) return "c is negative" // some condition, value
return "otherwise this"
}
foo().then(result => console.log(result))
What's nice about this is – aside from the code being very flat and readable (imo) – values a, b, and c are all available in the same scope. This means your conditions and return values can depend on any combination of your tasks' promised values.

Categories