How do I flatten a nested promise dependency? - javascript

I'm using mondora/asteroid through a node app to use Meteor DDP via a promise pattern.
I have the following code I am rewriting from callback style, into a promise style, but am stuck on how to flatten it.
asteroid.call('Pony.search', { params })
.then(res => {
if (res.length === 1) {
// something
asteroid.call('Pony.finish', { params })
// this part feels wrong
.then(res => {
// something else
});
} else {
// nope
}
})
.catch(err => {
console.error(err);
});
There is a second asteroid.call & then inside the first promise response which is a promise. This part feels wrong, like it should be flat and not nested but I'm not sure how to get there.
edit:
Ended up using something like this (still not decided on whether to have the first then check for if length === 1 and potentially immediately reject it. Anyone know what is best practice on that?
asteroid.call('Pony.search', { params })
.then(res => res.length === 1 ? res : Promise.reject())
.then(res => asteroid.call('Pony.finish', { res[0].something }))
.then(res => {
// do something
})
.catch(err => {
// handle the no found
console.error(err);
});

Instead of nesting callbacks, chain promises together with .then()
A note:
I'm not sure what Promise library you are using, but the idea is to return a rejected promise from the first .then() if there is an error, otherwise you return a successful promise. The promise library will then handle the error handling for you, going to the catch block if there is a rejected promise.
asteroid.call('Pony.search', { params })
.then(res => {
res.length === 1 ? return asteroid.call('Pony.finish', { params }) : Promise.reject();
})
.then(res => {
//do stuff here
})
.catch(err => {
console.error(err);
});
edit:
The only issue is when you need to access both of the return values from the promises at the same time. When you flatten out a promise chain you lose access to the results from the previous promises.
You have a few options:
If you don't need the previous result then flatten out the promise chain like I did here
If you do need the previous value
2a. And you don't care about the ordering of execution then use Promise.all([promise1, promise2])
2b. And you do care about the ordering of the execution then you must use nested promises like you originally did.

If promises are nested, there is no difference between promises and callback.
Try changing your code to use promise chain:
asteroid.call('Pony.search', { params })
.then(res => {
if (res.length === 1) {
// something
let p1 = asteroid.call('Pony.finish', { params });
return p1;
}
else {
// nope
}
})
.then (function SomeThingElse(res2){
// something else
// res2 is return value from nested asteroid.call
})
.catch(err => {
console.error(err);
});
Since the first resolve handler returns a Promise p1, the invocation of the next functions in the chain (function SomeThingElse) is deferred until p1 is resolved.
Now expanding this example:
asteroid.call('Pony.search', { params })
.then(res => {
if (res.length === 1) {
// something
let p1 = asteroid.call('Pony.finish', { params });
return p1;
}
else {
// nope
}
})
.then (function SomeThingElse(res2){
// something else
// res2 is return value from nested asteroid.call
})
.then (function AnotherFunc(res3){
})
.catch(err => {
console.error(err);
});
If SomeThingElse returns a Promise, invocation of AnotherFunc is delayed until that promise is resolved.
If SomeThingElse does not return a Promise, AnotherFunc would be invoked immediately with the same parameters as 'SomeThingElse' received. In other words, both SomeThingElse and AnotherFunc are invoked when p1 is resolved.

Related

What does a Promise guarded by a catch() return?

I have an asynchronous function that is called at the beginning of my code, and only when it is done further things can happen. It would typically be something like
const initialCheck = () => {
return fetch('https://reqres.in/api/users')
.then(r => {
// do things
return r
})
}
const actualStuff = () => {
console.log('hello')
}
initialCheck()
.then(r => {
actualStuff()
})
The goal of this initial check is to make sure that the HTTP endpoint (https://reqres.in/api/users) is actually reachable. It should therefore account for a connection issue and my solution was to catch() the possible error:
const initialCheck = () => {
return fetch('https://thissitedoesnotexistihopeatleast')
.then(r => {
// do things when the endpoint is available
return r
})
.catch(err => {
// do something when the endpoint is not availble
// nothing is returned
})
}
const actualStuff = () => {
console.log('hello')
}
initialCheck()
.then(r => {
actualStuff()
})
My question: why does the code above work?
catch() is executed (and the then() in that unction is not), it does not return anything, and despite that .then(r => {actualStuff()}) outputs what is expected (hello on the console).
What does that then() actually receives? (that I do not return)
A .catch chained onto a Promise will result in a Promise that:
resolves with the value returned from the .catch, if the .catch returns normally
rejects with the error, if the .catch itself throws an error
So
const chainedPromise = somePromise.catch(() => {
// no errors here
// nothing returned
})
If the .catch definitely doesn't throw, then chainedPromise will definitely resolve (and not reject), and since nothing is returned, the chainedPromise will resolve to undefined.
So
initialCheck()
.then(r => {
actualStuff()
})
works because initialCheck returns a Promise that resolves (to undefined).
If you returned something from the .catch, and the .catch was entered into, you'd see it onto .thens chained onto it later:
const initialCheck = () => {
return fetch('https://doesnotexist')
.catch(() => {
return 'foo';
});
}
const actualStuff = (r) => {
console.log(r);
console.log('r is foo:', r === 'foo')
}
initialCheck()
.then(r => {
actualStuff(r);
})

What's the promise chaining equivalent of awaiting multiple async functions?

I'm studying the usage of promsies and async/await.
I've wrote the following code, which does the following:
It gets some database's data (using Knex.js),
Handles that data,
Assigns the handled data into a specified property.
These 3 steps are done multiple times (In the following code, it's done twice), and are always awaited:
async function run() {
return await getData();
}
async function getData() {
let handledData = {};
handledData.res1 = await knex.select('column1').from('table1').where('column1', '1')
.then(data => handleData(data))
.catch(handleError);
handledData.res2 = await knex.select('column1').from('table1').where('column1', '2')
.then(data => handleData(data, handledData))
.catch(handleError);
return handledData;
}
async function handleData(data) {
let res = [];
data.forEach(item => {
res.push(item.column1);
});
return res;
}
function handleError (error) {
console.log(error);
}
Now, I'm trying to write the promise-chaining equivalent of getData, and this is what I came up with:
async function getData() {
let handledData = {};
let promise = new Promise(function(resolve, error){ resolve(); });
promise
.then(function () {
return knex.select('column1').from('table1').where('column1', '1')
.then(data => handleData(data))
.catch(handleError);
})
.then(function(handled){
handledData.res1 = handled;
return knex.select('column1').from('table1').where('column1', '2')
.then(data => handleData(data))
.catch(handleError);
})
.then(function(handled){
handledData.res2 = handled;
return handledData;
})
.catch(handleError);
return promise;
}
But this doesn't quite work. What happens is that after the first then returns, the await inside run ends its awaiting, which causes run to return - and only then the second then is executed.
How can I make the promise-chaining version work as the multiple-await version does?
(and please, feel free to point out any misunderstaings I made of promises/async-await)
If possible, I'd recommend using Promise.all instead, it'll make your script run faster in addition to making the logic clearer:
const getData = Promise.all([
knex.select('column1').from('table1').where('column1', '1')
// Simply pass the function name as a parameter to the `.then`:
.then(handleData)
.catch(handleError),
knex.select('column1').from('table1').where('column1', '2')
.then(handleData)
.catch(handleError)
])
.then(([res1, res1]) => ({ res1, res2 }));
knex.select().then() returns a promise, so you don't need to wrap it in another promise you just need to set up the chain of then()s and return the whole thing. The result will be that getData returns the promise from the last then. You can return the value you want from that then() which will make it available to the caller. For example:
function run() {
getData()
.then(handledData => console.log(handledData) /* do something with data */)
}
function getData() {
let handledData = {};
// need to return this promise to callers can access it
return knex.select('column1').from('table1').where('column1', '1')
.then(data => handledData.res1 = handleData(data))
.then(() => knex.select('column1').from('table1').where('column1', '2'))
.then(data => {
handledData.res2 = handleData(data)
return handledData
})
.catch(handleError);
}
You could also set this up to pass the handledData object thought the chain, but you don't need to in this case.
The function handleData() is synchronous, so you don't need to make it an async function.

How to nest promises and catch error in outer promise

I am writing a unit test to ensure that catch works in the following function:
function myFunction(){
const myPromises = Promise.all(getMyPromises())
return Promise.all(myPromises
.then( objArray => {
return Promise.all(Promise.map(objArray,
doSomethingWithPromises()
)).then(response => {
getSomeMorePromises()
return response;
})
.catch(err => {
doSomethingWhenErr();
});
})
) .catch(err => {
doSomethingWhenErr();
});
}
I am unable to catch the error from the inner catch in the outer therefor my unit test is failing.
I have tried excluding the catch from the inner function however that didn't work.
How do I ensure that if a promise is not resolved in the getSomeMorePromises() the error is returned and catched in the outer?
Promise chains are formed from promises that you return from your then() callbacks.
Merely creating a promise inside a then() callback doesn't link to it or handle its errors in any way.
You need to return those promises:
function myFunction() {
const myPromises = Promise.all(getMyPromises())
return myPromises.then(objArray => {
return Promise.all(objArray.map(getOtherPromise));
)).then(response => {
return Promise.all(getSomeMorePromises());
});
}

Break promise chain when condition is not met [duplicate]

How should I stop the promise chain in this case?
Execute the code of second then only when the condition in the first then is true.
var p = new Promise((resolve, reject) => {
setTimeout(function() {
resolve(1)
}, 0);
});
p
.then((res) => {
if(true) {
return res + 2
} else {
// do something and break the chain here ???
}
})
.then((res) => {
// executed only when the condition is true
console.log(res)
})
You can throw an Error in the else block, then catch it at the end of the promise chain:
var p = new Promise((resolve, reject) => {
setTimeout(function() {
resolve(1)
}, 0);
});
p
.then((res) => {
if(false) {
return res + 2
} else {
// do something and break the chain here ???
throw new Error('error');
}
})
.then((res) => {
// executed only when the condition is true
console.log(res)
})
.catch(error => {
console.log(error.message);
})
Demo - https://jsbin.com/ludoxifobe/edit?js,console
You could read the documentation, which says
Promise.then return a rejected Promise if the input function throws an error, or the input function returns a rejected Promise.
If you prefer, you could read the Promise A spec, in the section about then, where promise2 refers to the resulting promise:
If either onFulfilled or onRejected throws an exception e, promise2 must be rejected with e as the reason.)
If you prefer, you could read the excellent 2ality blog:
then() returns a new promise Q (created via the constructor of the receiver):
If either of the reactions returns a value, Q is resolved with it.
If either of the reactions throws an exception, Q is rejected with it.
You could read the brilliant YDKJS:
A thrown exception inside either the fulfillment or rejection handler of a then(..) call causes the next (chained) promise to be immediately rejected with that exception.
You could move the chain into the conditional branch:
p.then((res) => {
if(true) {
return Promise.resolve(res + 2).then((res) => {
// executed only when the condition is true
});
} else {
// do something
// chain ends here
}
});
Just use something like: reject('rejected')
in the else of the first task.
P
.then((res) => {
if(true) {
return res + 2
} else {
reject('rejected due to logic failure' }
})
.then((res) => {
// executed only when the condition is true
console.log(res)
})
Alternatively u can also add a catch section to ur first task with .catch()
Hope this helps.

How do I break a promise chain?

How should I stop the promise chain in this case?
Execute the code of second then only when the condition in the first then is true.
var p = new Promise((resolve, reject) => {
setTimeout(function() {
resolve(1)
}, 0);
});
p
.then((res) => {
if(true) {
return res + 2
} else {
// do something and break the chain here ???
}
})
.then((res) => {
// executed only when the condition is true
console.log(res)
})
You can throw an Error in the else block, then catch it at the end of the promise chain:
var p = new Promise((resolve, reject) => {
setTimeout(function() {
resolve(1)
}, 0);
});
p
.then((res) => {
if(false) {
return res + 2
} else {
// do something and break the chain here ???
throw new Error('error');
}
})
.then((res) => {
// executed only when the condition is true
console.log(res)
})
.catch(error => {
console.log(error.message);
})
Demo - https://jsbin.com/ludoxifobe/edit?js,console
You could read the documentation, which says
Promise.then return a rejected Promise if the input function throws an error, or the input function returns a rejected Promise.
If you prefer, you could read the Promise A spec, in the section about then, where promise2 refers to the resulting promise:
If either onFulfilled or onRejected throws an exception e, promise2 must be rejected with e as the reason.)
If you prefer, you could read the excellent 2ality blog:
then() returns a new promise Q (created via the constructor of the receiver):
If either of the reactions returns a value, Q is resolved with it.
If either of the reactions throws an exception, Q is rejected with it.
You could read the brilliant YDKJS:
A thrown exception inside either the fulfillment or rejection handler of a then(..) call causes the next (chained) promise to be immediately rejected with that exception.
You could move the chain into the conditional branch:
p.then((res) => {
if(true) {
return Promise.resolve(res + 2).then((res) => {
// executed only when the condition is true
});
} else {
// do something
// chain ends here
}
});
Just use something like: reject('rejected')
in the else of the first task.
P
.then((res) => {
if(true) {
return res + 2
} else {
reject('rejected due to logic failure' }
})
.then((res) => {
// executed only when the condition is true
console.log(res)
})
Alternatively u can also add a catch section to ur first task with .catch()
Hope this helps.

Categories