Execute a promise loop and catch errors along the way - javascript

So here is a true beauty from #Roamer-1888:
executePromiseLoop(myArray).catch(logError).then(() => console.log('yay!'));
function executePromiseLoop(arr) {
return arr.reduce(function(promise, email) {
return promise.then(function() {
return myCustomPromise(email);
});
}, Promise.resolve());
}
It's a promise loop that executes serially. There are two concerns I have:
What happens if a promise in the loop fails, does it quit the loop?
Should I implement a catch inside the loop, or will a fail propagate back up to the calling function?
Should I insert a catch in the loop?
function executePromiseLoop(arr) {
return arr.reduce(function(promise, email) {
return promise.catch(logError).then(function() {
return myCustomPromise(email);
});
}, Promise.resolve());
}

What happens if a promise in the loop fails, does it quit the loop?
Yes. If a single promise is rejected, all the next promises will not be executed. See this code for example:
Promise.resolve()
.then(Promise.reject)
.then(() => console.log('this will not be executed'))
.catch(() => console.log('error'))
The promise in the third line will not be executed, because the promise before it was rejected.
Should I implement a catch inside the loop, or will a fail propagate back up to the calling function?
The reject message will propagate, so you don't need to use a catch inside the loop. See this code for example:
Promise.resolve()
.then(() => Promise.resolve())
.then(() => Promise.reject('something went wrong'))
.then(() => Promise.resolve())
.then(() => Promise.resolve())
.catch(error => console.log(error))

Related

How to handle exception resulting in unresolved promise

I'm seeing some inconsistent behaviour when dealing with a promise that fails to resolve due to an unforeseen uncaught exception. It seems that depending on how I chain the promise changes whether this promise resolves and I don't understand why.
Here is the bad function:
function bad() {
return new Promise(resolve => {
setTimeout(() => {
throw 'unforseen exception!';
resolve();
}, 50);
});
}
If I call this function these ways it does not resolve:
bad().then(() => console.log('resolved')); // no console logging
try {
await bad();
} catch(e) {
console.log(e);
}
console.log('resolved'); // no console logging
But calling it like this does resolve:
Promise.resolve().then(bad()).then(() => console.log('resolved')); // console logs "resolved"
Why is this? Edit: I now understand what I was doing wrong. But what I really want to answer is the next part.
And how do I best protect myself against unforeseen exceptions when I have a chain of promises that need to be run serially and need to continue even if there is a failure somewhere along the chain?
I have also tried using catch or finally but they don't seem to make any difference. Once that unresolved promise is reached, execution fails.
The problem is that bad() throws an error asynchronously in such a way that the error can't be detected by the caller. If you want to throw an error inside a new Promise... segment, you should call the reject function:
function bad() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('bad!');
resolve();
}, 50);
});
}
bad()
.then(() => console.log('resolved'))
.catch((err) => console.log(err));
(async () => {
try {
await bad();
} catch(e) {
console.log(e);
}
console.log('await finished');
})();
The reason your
Promise.resolve().then(bad()).then
calls the next .then is because then accepts a function as a parameter, but your bad() is invoking bad in the beginning, while the interpreter is trying to come up with the Promise chain. If you had passed bad as the function parameter instead of calling it, you would see similar broken behavior as in your original code - the Promise would never resolve:
function bad() {
return new Promise(resolve => {
setTimeout(() => {
throw 'unforseen exception!';
resolve();
}, 50);
});
}
// Promise never resolves:
Promise.resolve().then(bad).then(() => console.log('resolved'));
In contrast, .then(bad()) will evaluate to a non-function, and hence that .then will resolve immediately, so the interpreter will go on to the next .then immediately as well.
In this code:
new Promise(resolve => {
setTimeout(() => {
throw 'unforseen exception!';
resolve();
}, 50);
});
The throw is happening in a non-async callback function. The way to handle something like this would be to use a try/catch statement on the code that could throw:
new Promise((resolve, reject) => {
setTimeout(() => {
try {
throw 'unforseen exception!';
resolve();
}
catch (err) {
reject(err);
}
}, 50);
});

Error propagation in chained Promise not working as expected

I'm learning chaining in JS Promises from this site. Based on the high level example, I've written following code to understand error propagation better.
var promise = new Promise((resolve, reject) => {
reject('Rejected!');
});
promise.then(()=>new Promise ((resolve, reject) => resolve('Done!')), () => console.log('Failure of first Promise'))
.then(() => console.log('Success of nested Promise'), () => console.log('Failure of nested Promise'));
console.log('This will be still printed first!');
Here when I'm rejecting the first promise, It is giving logging Failure of first Promise and then Success of nested Promise.
Now I wonder how It's going in the success callback of nested Promise? As explained in the above mentioned article, it's clear that even one promise is failed (rejected), the failure callback should be invoked.
What I'm missing here? Thanks.
The code you have written is like
var promise = new Promise((resolve, reject) => { reject('Rejected!'); });
promise
.then(()=>new Promise ((resolve, reject) => resolve('Done!')))
.catch(() => console.log('Failure of first Promise'))
.then(() => console.log('Success of nested Promise'))
.catch(() => console.log('Failure of nested Promise'));
console.log('This will be still printed first!');
since catch also returns a promise, the then chained with catch will also be triggered, if there is only one catch at end of all then, that catch will be triggered without any then.
you can do the following to overcome this issue,
promise
.then(()=>new Promise ((resolve, reject) => resolve('Done!')))
.then(() => console.log('Success of nested Promise'))
.catch(() => console.log('Failure of Promise'));
console.log('This will be still printed first!');
Second then callback catches the error from previous promise (catches as in try..catch). In this specific case (there's no chance that first then callbacks result in rejection) this is the same as:
promise // rejected promise
.then(()=>new Promise ((resolve, reject) => resolve('Done!'))) // skips rejected promise
.catch(() => console.log('Failure of first Promise')) // results in resolved promise
.then(() => console.log('Success of nested Promise')) // chains resolved promise
.catch(() => console.log('Failure of nested Promise')); // skips resolved promise
This is because
The catch() method returns a Promises
MDN source - Promise.prototype.catch()
"use strict";
new Promise((resolve, reject) => {
reject('Error');
}).catch(err => {
console.log(`Failed because of ${err}`);
}).then(() => {
console.log('This will be called since the promise returned by catch() is resolved');
});
This will log
Failed because of Error
This will be called since the promise returned by catch() is resolved

Value of Promise.all() after it is rejected, shows [''PromiseStatus'']: resolved if catch block is present

I have two promises, one rejected and other resolved. Promise.all is called. It executed the catch block of Promise.all as one of the promises is rejected.
const promise1 = Promise.resolve('Promise 1 Resolved');
const promise2 = Promise.reject('Promise 2 Rejected');
const promise3 = Promise.all([promise1, promise2])
.then(data => {
console.log('Promise.all Resolved', data);
})
.catch(error => {
console.log('Promise.all REJECTED', error);
})
setTimeout(() => {
console.log(promise1, promise2, promise3)
}, 200);
If I don't have the catch on Promise.all(), the value remains as Rejected, ie
const promise3 = Promise.all([promise1, promise2])
.then(data => {
console.log('Promise.all Resolved', data);
})
Am I missing something about promises.
I see that its answer but I think I can clarify a bit more.
Please remember that each then() or catch() return a Promise. (If you don't have any explicit return in callback, both will return Promise.resolve(undefined)). Therefore after the promise has resolved, the value of entire promise chain will be the promise returned by last then();
Example:
promise = Promise.resolve(1)
.then(() => Promise.resolve(2))
.then(() => Promise.resolve(3));
console.log(promise);
setTimeout(() => {
console.log(promise)//PromiseĀ {<resolved>: 3}
}, 0)
catch() works in exactly like then(). The only difference is that its called on rejected promises rather then resolved.
In following example, I just replace all resolve by reject to demonstrate that.
promise = Promise.reject(1)
.catch(() => Promise.reject(2))
.catch(() => Promise.reject(3));
console.log(promise);
setTimeout(() => {
console.log(promise)//Promise {<rejectd>: 3}
}, 0)
Now coming to your question. Value of Promise.all() is a rejected promise, since one of the promise in array is rejected. If you have a catch block in chain, control will go to that catch block which will return a Promise.resolve(undefined). If you have no catch block in the chain, you will get what you have: a rejected promise.
A catch on a Promise acts the same as a try {} catch {} block, in that you have captured the error state and the program will continue to function as normal.
That's why, when you omit the catch, your promise state is "rejected".
If, after having caught the error, you want to return the promise state as rejected you need to return a rejected promise from the catch handler:
const promise3 = Promise.all([promise1, promise2])
.catch(error => {
console.log("REJECTED", error);
return Promise.reject(error);
});
console.log(promise3); // [[PromiseStatus]]: "rejected"
Similar to doing a throw inside a try {} catch { throw; } block

Adding a promise to sequence if condition is met

I have a Promise, Promise A, which 'checks' some condition asynchronously. It is always resolved (no rejection), so only then is necessary. The resolve is either true or false (the aforementioned condition).
If the condition is not met (resolve(false)) in the first promise, a second promise, Final Promise is then executed. However, if the initial condition was met (resolve(true)), an additional promise is executed 'between the two': Promise B.
Because Final Promise produces two callbacks (then and catch), I am trying to avoid code duplication. This is what I'm using, which works, but the final callbacks are duplicated.
promiseA().then((condition) => {
if (condition) {
promiseB().then(finalPromise().then(() => {
console.log('final promise success');
}).catch(() => {
console.log('final promise failure');
}));
} else {
finalPromise().then(() => {
console.log('final promise success');
}).catch(() => {
console.log('final promise failure');
});
}
});
I tried the following, but it doesn't work: Final Promise is being executed before Promise B.
promiseA().then((condition) => {
if (condition) {
return promiseB();
}
}).then(finalPromise().then(() => {
console.log('final promise success');
}).catch(() => {
console.log('final promise failure');
}));
Additionally, I tried using the following, but then Final Promise wouldn't be executed after Promise B:
promiseA().then((condition) => {
if (condition) {
return promiseB();
}
}).then(finalPromise).then(() => {
console.log('final promise success');
}).catch(() => {
console.log('final promise failure');
});
I'm guessing I could store everything that is inside the else branch (in the first snippet) into a variable and use it for both cases. I'm wondering if there's a solution built into the Promise API.
I figured it out. The third snippet works, but I needed to 'manually' resolve Promise B to continue the chain (I learned something new!).
function promiseB() {
return new Promise((resolve) => {
console.log('Promise B execution');
resolve(); // adding this solved it
});
}
The queue snippet:
promiseA().then((condition) => {
if (condition) {
return promiseB();
}
}).then(finalPromise).then(() => {
console.log('final promise success');
}).catch(() => {
console.log('final promise failure');
});

Promise chain with an asynchronous operation not executing in order

I have found other people asking about this topic but I haven't been able to get my promise chain to execute in order.
Here is a basic reproduction of what is happening:
function firstMethod(){
dbHelper.executeQuery(queryParameters).then(result => {
if (result === whatIAmExpecting) {
return dbHelper.doDbOperation(secondQueryParameters)}
else {
throw new Error('An error occurred')
}})
.then(doFinalOperation())
.catch(error => {
})
}
In the above code doFinalOperation() is called before the then function after executeQuery() is called.
Here is the implementation of executeQuery():
function executeQuery(parameter) {
return new Promise((resolve, reject) => {
const queryToExecute = `SELECT * FROM parameter`
return mySqlConnection.query(queryToExecute).then((result) => {
resolve(result)
}).catch(error => {
reject(error)
})
})
And here is the implementation of of the mySqlConnection.query method:
function query(queryString){
return new Promise((resolve, reject) =>
{
initConnection()
connection.connect()
require('bluebird').promisifyAll(connection)
return connection.queryAsync(queryString).then(function(results) {
connection.end();
resolve(results)
}).catch(error => {
reject(error)
})
})
It seems like I have incorrectly implemented the executeQuery() method. The database operation in the mySqlConnection.query is asychronous and I can see that's where the chain of promises stops happening in the expected order.
My question in a nutshell: How do I make the my chain of promises execute in order, and how I do stop a then() method from being executed before the previous Promise has called resolve() or reject()?
Thanks in advance.
then expects a function, but you have accidentally executed it, instead of passing it. Change:
then(doFinalOperation())
with:
then(doFinalOperation)
Now it will be the promise implementation that invokes it (at the proper time), not "you".
If your function needs arguments to be passed, then you can either
(1) Use bind:
then(doFinalOperation.bind(null, parameterOne, parameterTwo, parameterThree))
(2) Use a function expression
then(_ => doFinalOperation(parameterOne, parameterTwo, parameterThree))
Both your .then() methods get called on the first async operation...
Should be something like this:
function firstMethod(){
dbHelper.executeQuery(queryParameters).then(expectedResult => {
if (expectedResult === whatIAmExpecting) {
return dbHelper.doDbOperation(secondQueryParameters)}
.then(doFinalOperation())
.catch(error => {
};
}
else {
throw new Error('An error occurred')
}})
.catch(error => {
});
}

Categories

Resources