Optional catch in javascript promises - javascript

The following is valid:
new Promise<void>((resolve, reject) => {
reject()
})
.then(() => {})
.catch(() => {})
But I might not always care about the error. Is there a way to make the catch optional?
I tried this but it didn't work:
new Promise<void>((resolve, reject?) => {
if (reject) reject()
})
.then(() => {})
Error: Uncaught (in promise): undefined

Is there a way to make the catch optional?
No. If you are using a promise that might error, you need to handle that (or propagate it to your caller).
Of course if you create the promise yourself, rejecting it is optional, and you can choose to never reject your promises so that you won't need to handle any errors. But if there are errors from promises that you are using, and you want to ignore them, you must do so explicitly. Just write
somePromise.catch(e => void e);
// or () => { /* ignore */ }
// or function ignore() {}

I was trying to solve the same issue, and finally come up with the following promise wrapper:
/**
* wraps a given promise in a new promise with a default onRejected function,
* that handles the promise rejection if not other onRejected handler is provided.
*
* #param customPromise Promise to wrap
* #param defaultOnRejected Default onRejected function
* #returns wrapped promise
*/
export function promiseWithDefaultOnRejected(customPromise: Promise<any>, defaultOnRejected: (_: any) => any): Promise<any> {
let hasCatch = false;
function chain(promise: Promise<any>) {
const newPromise: Promise<any> = new Promise((res, rej) => {
return promise.then(
res,
function(value) {
if (hasCatch) {
rej(value);
} else {
defaultOnRejected(value);
}
},
);
});
const originalThen = newPromise.then;
// Using `defineProperty` to not overwrite `Promise.prototype.then`
Object.defineProperty(newPromise, 'then', {
value: function (onfulfilled: any, onrejected: any) {
const result: Promise<any> = originalThen.call(newPromise, onfulfilled, onrejected);
if (typeof onrejected === 'function') {
hasCatch = true;
return result;
} else {
return chain(result);
}
}
});
return newPromise;
}
return chain(customPromise);
}
This function lets you wrap your promises with a defaultOnRejected function that will handle the rejected promise if no other handler is provided. For example:
const dontCare = promiseWithDefaultOnRejected(Promise.reject("ignored"), () => {});
The result promise will never throw an "Unhandled Promise Rejection", and you can use it as follows:
dontCare.then(x=>console.log("never happens")).catch(x=>console.log("happens"));
or
dontCare.then(x=>console.log("never happens"), x=>console.log("happens"));
or simply without onRejected handler:
dontCare.then(x=>console.log("never happens")).then(x=>console.log("also never happens"));
An issue with this util is that it is not working as expected with async/await syntax: you need to propagate and handle the "catch" path as follows:
async () => {
try {
await promiseWithDefaultOnRejected(Promise.reject("ignored"), () => {})
.catch((e) => { throw e; });
} catch (e) {
console.log("happens");
}
}

You could resolve when the error is something you don't care about. If your catch returns anything other than a rejected promise, the error isn't propagated down the chain.
const ignorableError = new Error("I don't care about this error");
const myPromise = new Promise((resolve, reject) => {
reject(ignorableError);
})
.then(() => {})
.catch(error => {
if(error == ignorableError) {
console.log("Error ignored");
return;
}
// Do something else...
});
myPromise.then(() => console.log("Success"))

Let me try to describe your situation:
You have a service that gets user information and a function called getUser that uses that service. When the service fails for any reason then getUser does not have a user available. The result of getUser is used quite a lot of times in your code with the following situation(s):
User is available run a function (block of code).
User is not available run a function (block of code).
Run a function with the error/reject of the service.
When using getUser result you may want to run all 3 functions, a combination of 2 of them or only one.
Current getUser returns a Promise and this type does not seem to be suitable for your situation. Mainly because rejecting a promise and not catching it will cause unhandled promise rejection. And because if you want to run code if user is available or not it complicates the functions (they both have to check the result instead of assuming user is or is not available).
Maybe you can try the following code, please be careful making assumptions in the not available block, this could be due to any error. For example: it does not mean the user does not exist because it could be a network error.
In the example getUser is used but can be any function that returns a promise where you assume not available on reject.
const isAvailable = promise => {
//do not expose NOT_AVAILABLE outside this function
const NOT_AVAILABLE = {type:"not available"};
//no uncaught promise rejection errors by VM
const savePromise = promise.catch(x=>x);
return {
available:fn=>
promise
.catch(e=>Promise.reject(NOT_AVAILABLE))
.then(fn)
.catch(
e=>
(e===NOT_AVAILABLE)
? undefined//ignore
: Promise.reject(e)//re throw, error is not from service
),
//call not available with the promise if promise rejects
notAvailable:fn=>promise.catch(()=>fn(promise)),
catchError:promise.catch.bind(promise)
};
}
const service = arg =>
(arg===1)
? Promise.resolve(arg)
: Promise.reject(arg)
const getUser = arg => isAvailable(service(arg));
var user = getUser(2);
//if service failed available will be skipped
user.available(
user=>console.log("skipped:",user)
);
//both catch and notAvailable will be called
user.notAvailable(
arg=>console.log("not available:",arg)
);
user.notAvailable(
arg=>console.log("still not available:",arg)
);
//not handling catchError does not cause uncaught promise exception
// but you can inspect the error
// user.catchError(
// err=>console.log("error is::",err)
// );
var user = getUser(1);
//you can call available on user multiple times
user.available(
user=>console.log("got user:",user)
);
user.available(
user=>Promise.resolve(user)
.then(user=>console.log("still got user:",user))
.then(x=>Promise.reject("have to catch this one though"))
.catch(e=>console.log("ok, got error trying to run available block:",e))
);
//showing you can inspect the error
var user = getUser(5);
user.catchError(err=>console.log("error from service:",err));

Related

JavaScript Promise : Catching errors within chained functions

I have a function that is used to add a record to the IndexDb database:
async function addAsync(storeName, object) {
return new Promise((res, rej) => {
// openDatabaseAsync() is another reusable method to open the db. That works fine.
openDatabaseAsync().then(db => {
var store = openObjectStore(db, storeName, 'readwrite');
var addResult = store.add(JSON.parse(object));
addResult.onsuccess = res;
addResult.onerror = (e) => {
console.log("addResult Error");
throw e;
};
}).catch(e => {
// Error from "throw e;" above NOT GETTING CAUGHT HERE!
console.error("addAsync ERROR > ", e, storeName, object);
rej(e);
});
})
}
If I try to add a duplicate key, then I expect:
addResult.onerror = (e) => {
console.log("addResult Error");
throw e;
}
to capture that. It does.
But then, I also expect my
.catch(e => {
// Error from "throw e;" above NOT GETTING CAUGHT HERE!
console.error("addAsync ERROR > ", e, storeName, object);
rej(e);
})
to catch that error. But instead I get an "uncaught" log.
Console output:
addResult Error
Uncaught Event {isTrusted: true, type: "error", target: IDBRequest, currentTarget: IDBRequest, eventPhase: 2, …}
Does that final .catch only handle exceptions from the openDatabaseAsync call? I would have thought now as it is chained to the .then.
In summary, here's what I would expect from the above code:
If openDatabaseAsync() fails then I'm not catching that so the error would be sent to the caller of addAsync().
If .then fails then I expect the .catch to catch it, log the error and then reject the promise meaning that the called of addAsync() would need to handle that.
However, I would have thought that I should get the log from the line:
console.error("addAsync ERROR > ", e, storeName, object);
before the reject is sent back to the caller of addAsync(), which may be unhandled at that point.
Your approach would benefit form a larger overhaul.
Generally, don't write a function as async when it's not also using await.
Don't use new Promise() for an operation that returns a promise, such as openDatabaseAsync() does. Return that promise, or switch to async/await.
It would be useful to wrap IndexedDB operations so that they follow promise semantics.
On the example of IDBRequest:
function promisifyIDBRequest(idbr) {
return new Promise( (resolve, reject) => {
idbr.onsuccess = () => resolve(idbr.result);
idbr.onerror = (e) => reject(e.target.error);
});
}
Now you can do this:
async function addAsync(storeName, object) {
const db = await openDatabaseAsync();
const store = openObjectStore(db, storeName, 'readwrite');
return promisifyIDBRequest(store.add(JSON.parse(object)));
}
Add a try/catch block if you want to handle errors inside of addAsync().
It's worth checking out existing solutions that wrap the entire IndexedDB interface with promise semantics, such as https://github.com/jakearchibald/idb.
FWIW, the promise-chain variant of the above function would look like this:
function addAsync(storeName, object) {
return openDatabaseAsync().then( (db) => {
const store = openObjectStore(db, storeName, 'readwrite');
return promisifyIDBRequest(store.add(JSON.parse(object)));
});
}
both variants return a promise for an IDBRequest result.

Google Cloud Functions - Retry

I have this idempotent function with multiple promises that I wrote for Google Cloud Functions.
I want to have retry enabled since my used API is pretty inconsistent. This requires a rejected promise to be returned when a retry is needed.
Therefore I tried to return a promise.all([]) but that does not terminate/stop the function when one of the promises fails. It then even proceeds to the promise.all().then()? This should only happen when all 4 promises are successful.
Who can point me in the right direction? Does it even make sense what I'm trying?
exports.scheduleTask = functions
.firestore.document("tasks_schedule/{servicebonnummer}")
.onCreate((snap, context) => {
servicebonnummer = snap.data().data.servicebonnummer;
bondatum = snap.data().data.bondatum;
servicestatus = snap.data().data.servicestatus;
tijdstip = snap.data().data.tijdstip;
firestorePromise = null;
firestoreFinish = null;
cashPromise = null;
cashFinish = null;
//Firebase
//firestoreSchedule executes the required promise
//checkFinished points to a database where it checks a boolean for idempotency
//firestoreFinish writes to this database and sets the boolean to true when the promise is successful
if (!checkFinished("tasks_schedule", servicebonnummer, "firestore")) {
firestorePromise = scheduleFirestore(
servicebonnummer,
bondatum,
servicestatus,
tijdstip
)
.then(output => {
firestoreFinish = markFinished(
"tasks_schedule",
servicebonnummer,
"firestore"
);
return output;
})
.catch(error => {
console.error(
"scheduleFirestore - Error connecting to Firestore: ",
error
);
return error;
});
}
//SOAP API
//cashSchedule executes the required promise
//checkFinished points to a database where it checks a boolean for idempotency
//cashFinish writes to this database and sets the boolean to true when the promise is successful
if (!checkFinished("tasks_schedule", servicebonnummer, "cash")) {
cashPromise = scheduleCash(
servicebonnummer,
moment(bondatum),
servicestatus,
tijdstip
)
.then(result => {
if (result[0].response.code === "2") {
cashFinish = markFinished(
"tasks_schedule",
servicebonnummer,
"cash"
);
return result;
}
throw new Error("Validation error, response not successful");
})
.catch(error => {
console.error("scheduleCash - Error connecting to CASH API: ", error);
return error;
});
}
//CHECK PROMISES
return Promise.all([
firestorePromise,
firestoreFinish,
cashPromise,
cashFinish
])
.then(result => {
removeTask("tasks_schedule", servicebonnummer);
return result;
})
.catch(error => {
console.error("scheduleTask - Retry: ", error);
return error;
});
});
If you code:
let somePromise = new Promise(...);
return somePromise.then(funcA).catch(funcB);
Then you are indeed returning a promise. However, since you have handlers for that promise in your code, we need to look at what happens in more detail. Let us assume that somePromise is rejected. This will mean that the catch() handler will be invoked. It is the outcome of that catch handler that will be the ultimate resolution of the returned promise.
If we look at the MDN docs for Promise.catch() we find the following:
The Promise returned by catch() is rejected if onRejected throws an
error or returns a Promise which is itself rejected; otherwise, it is
resolved.
If we look at your code,
catch(error => {
console.error("scheduleTask - Retry: ", error);
return error;
});
And now ask:
Does this code throw an error? Nope ... it has no throw statement in it and hence just returns the values passed in.
Does the code return a Promise? Nope ... it is passed an error value and simply returns that error value which I am pretty sure will not itself be a Promise.
This means that the overall Promise returned is concluded in a resolved state and not a rejected state and hence the overall Cloud Function is considered to have concluded and is not retried.
Your options may be:
catch(error => {
console.error("scheduleTask - Retry: ", error);
throw error;
});
or
catch(error => {
console.error("scheduleTask - Retry: ", error);
return Promise.reject(error);
});
References:
Promise.prototype.catch()
Promise.reject()

Improved way to deal with callbacks inside promises

I have the following code that uses callbacks inside promises:
const clue = 'someValue';
const myFunction = (someParam, callback) => {
someAsyncOperation(someParam) // this function returns an array
.then((array) => {
if (array.includes(clue)){
callback(null, array); // Callback with 'Success'
}
else{
callback(`The array does not includes: ${clue}`); // Callback with Error
}
})
.catch((err) => {
// handle error
callback(`Some error inside the promise chain: ${err}`) // Callback with Error
})
}
and call it like this:
myFunction (someParam, (error, response) => {
if(error) {
console.log(error);
}
else {
// do something with the 'response'
}
})
Reading some documentation, I found that there is some improved way to do this:
const myFunction = (someParam, callback) => {
someAsyncOperation(someParam) // this function returns an array
.then((array) => {
if (array.includes(clue)){
callback(array);
}
else{
callback(`The array does not includes: ${clue}`);
}
}, (e) => {
callback(`Some error happened inside the promise chain: ${e}`);
})
.catch((err) => {
// handle error
callback(`Some error happened with callbacks: ${err}`)
})
}
My question:
In the sense of performance or best practices, it's okay to call the 'callback' function inside the promise as the two ways show or I'm doing something wrong, I mean some promise anti-pattern way ?
This seems really backwards and takes away from the benefits of promises managing errors and passing them down the chain
Return the asynchronous promise from the function and don't interrupt it with callbacks. Then add a catch at the end of the chain
const myFunction = (someParam) => {
// return the promise
return someAsyncOperation(someParam) // this function returns an array
.then((array) => {
return array.includes(clue) ? array : [];
});
}
myFunction(someParam).then(res=>{
if(res.length){
// do something with array
}else{
// no results
}
}).catch(err=>console.log('Something went wrong in chain above this'))
Do not use callbacks from inside of promises, that is an anti-pattern. Once you already have promises, just use them. Don't "unpromisify" to turn them into callbacks - that's moving backwards in code structure. Instead, just return the promise and you can then use .then() handlers to set what you want the resolved value to be or throw an error to set what you want the rejected reason to be:
const clue = 'someValue';
const myFunction = (someParam) => {
return someAsyncOperation(someParam).then(array => {
if (!array.includes(clue)){
// reject promise
throw new Error(`The array does not include: ${clue}`);
}
return array;
});
}
Then, the caller would just do this:
myFunction(someData).then(array => {
// success
console.log(array);
}).catch(err => {
// handle error here which could be either your custom error
// or an error from someAsyncOperation()
console.log(err);
});
This gives you the advantage that the caller can use all the power of promises to synchronize this async operation with any others, to easily propagate errors all to one error handler, to use await with it, etc...

How to make catched promises fail in jest

Consider this function:
aPromise = require('axios');
function middleware(callback) {
axios.get('/api/get')
.then(callback)
.catch(callback);
}
Consider this test:
const callback = (err) => {
expect(isError(err)).toBe(true);
done();
};
middleware(callback);
The isError is a lodash function.
Consider aPromise as something I want to test. If the promise always resolves, this test should not pass. But it will! And that's because the promise's catch actually catches the expect exception.
My question is: How to not catch the error in a promise's catch handler when expect throws an error in the promise's then handler?
Note that I don't use async/await.
You need to create a failed promise and you need to return the promise in your test. Please have a look on the doc on testing promises.
aPromise = require('axios');
jest.mock('axios', () => {
get: ()=> jest.fn() //initialy mock the get function
})
it('catch failing promises',() = > {
const result = Promise.reject('someError'); //create a rejected promises
aPromise.get.mockImplementation(() => result)// let `get` return the rejected promise
const callback = jest.fn()
middleware(callback)
return result
.then (()=>{
expect(callback).toHaveBeenCalledWith('someError');
})
})

Getting a UnhandledPromiseRejectionWarning when testing using mocha/chai

So, I'm testing a component that relies on an event-emitter. To do so I came up with a solution using Promises with Mocha+Chai:
it('should transition with the correct event', (done) => {
const cFSM = new CharacterFSM({}, emitter, transitions);
let timeout = null;
let resolved = false;
new Promise((resolve, reject) => {
emitter.once('action', resolve);
emitter.emit('done', {});
timeout = setTimeout(() => {
if (!resolved) {
reject('Timedout!');
}
clearTimeout(timeout);
}, 100);
}).then((state) => {
resolved = true;
assert(state.action === 'DONE', 'should change state');
done();
}).catch((error) => {
assert.isNotOk(error,'Promise error');
done();
});
});
On the console I'm getting an 'UnhandledPromiseRejectionWarning' even though the reject function is getting called since it instantly shows the message 'AssertionError: Promise error'
(node:25754) UnhandledPromiseRejectionWarning: Unhandled promise
rejection (rejection id: 2): AssertionError: Promise error: expected
{ Object (message, showDiff, ...) } to be falsy
should transition with the correct event
And then, after 2 sec I get
Error: timeout of 2000ms exceeded. Ensure the done() callback is
being called in this test.
Which is even weirder since the catch callback was executed(I think that for some reason the assert failure prevented the rest of the execution)
Now the funny thing, if I comment out the assert.isNotOk(error...) the test runs fine without any warning in the console. It stills 'fails' in the sense that it executes the catch.
But still, I can't understand these errors with promise. Can someone enlighten me?
The issue is caused by this:
.catch((error) => {
assert.isNotOk(error,'Promise error');
done();
});
If the assertion fails, it will throw an error. This error will cause done() never to get called, because the code errored out before it. That's what causes the timeout.
The "Unhandled promise rejection" is also caused by the failed assertion, because if an error is thrown in a catch() handler, and there isn't a subsequent catch() handler, the error will get swallowed (as explained in this article). The UnhandledPromiseRejectionWarning warning is alerting you to this fact.
In general, if you want to test promise-based code in Mocha, you should rely on the fact that Mocha itself can handle promises already. You shouldn't use done(), but instead, return a promise from your test. Mocha will then catch any errors itself.
Like this:
it('should transition with the correct event', () => {
...
return new Promise((resolve, reject) => {
...
}).then((state) => {
assert(state.action === 'DONE', 'should change state');
})
.catch((error) => {
assert.isNotOk(error,'Promise error');
});
});
For those who are looking for the error/warning UnhandledPromiseRejectionWarning outside of a testing environment, It could be probably because nobody in the code is taking care of the eventual error in a promise:
For instance, this code will show the warning reported in this question:
new Promise((resolve, reject) => {
return reject('Error reason!');
});
(node:XXXX) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Error reason!
and adding the .catch() or handling the error should solve the warning/error
new Promise((resolve, reject) => {
return reject('Error reason!');
}).catch(() => { /* do whatever you want here */ });
Or using the second parameter in the then function
new Promise((resolve, reject) => {
return reject('Error reason!');
}).then(null, () => { /* do whatever you want here */ });
I got this error when stubbing with sinon.
The fix is to use npm package sinon-as-promised when resolving or rejecting promises with stubs.
Instead of ...
sinon.stub(Database, 'connect').returns(Promise.reject( Error('oops') ))
Use ...
require('sinon-as-promised');
sinon.stub(Database, 'connect').rejects(Error('oops'));
There is also a resolves method (note the s on the end).
See http://clarkdave.net/2016/09/node-v6-6-and-asynchronously-handled-promise-rejections
The assertion libraries in Mocha work by throwing an error if the assertion was not correct. Throwing an error results in a rejected promise, even when thrown in the executor function provided to the catch method.
.catch((error) => {
assert.isNotOk(error,'Promise error');
done();
});
In the above code the error objected evaluates to true so the assertion library throws an error... which is never caught. As a result of the error the done method is never called. Mocha's done callback accepts these errors, so you can simply end all promise chains in Mocha with .then(done,done). This ensures that the done method is always called and the error would be reported the same way as when Mocha catches the assertion's error in synchronous code.
it('should transition with the correct event', (done) => {
const cFSM = new CharacterFSM({}, emitter, transitions);
let timeout = null;
let resolved = false;
new Promise((resolve, reject) => {
emitter.once('action', resolve);
emitter.emit('done', {});
timeout = setTimeout(() => {
if (!resolved) {
reject('Timedout!');
}
clearTimeout(timeout);
}, 100);
}).then(((state) => {
resolved = true;
assert(state.action === 'DONE', 'should change state');
})).then(done,done);
});
I give credit to this article for the idea of using .then(done,done) when testing promises in Mocha.
I faced this issue:
(node:1131004) UnhandledPromiseRejectionWarning: Unhandled promise rejection (re
jection id: 1): TypeError: res.json is not a function
(node:1131004) DeprecationWarning: Unhandled promise rejections are deprecated.
In the future, promise rejections that are not handled will terminate the Node.j
s process with a non-zero exit code.
It was my mistake, I was replacing res object in then(function(res), so changed res to result and now it is working.
Wrong
module.exports.update = function(req, res){
return Services.User.update(req.body)
.then(function(res){//issue was here, res overwrite
return res.json(res);
}, function(error){
return res.json({error:error.message});
}).catch(function () {
console.log("Promise Rejected");
});
Correction
module.exports.update = function(req, res){
return Services.User.update(req.body)
.then(function(result){//res replaced with result
return res.json(result);
}, function(error){
return res.json({error:error.message});
}).catch(function () {
console.log("Promise Rejected");
});
Service code:
function update(data){
var id = new require('mongodb').ObjectID(data._id);
userData = {
name:data.name,
email:data.email,
phone: data.phone
};
return collection.findAndModify(
{_id:id}, // query
[['_id','asc']], // sort order
{$set: userData}, // replacement
{ "new": true }
).then(function(doc) {
if(!doc)
throw new Error('Record not updated.');
return doc.value;
});
}
module.exports = {
update:update
}
Here's my take experience with E7 async/await:
In case you have an async helperFunction() called from your test... (one explicilty with the ES7 async keyword, I mean)
→ make sure, you call that as await helperFunction(whateverParams) (well, yeah, naturally, once you know...)
And for that to work (to avoid ‘await is a reserved word’), your test-function must have an outer async marker:
it('my test', async () => { ...
I had a similar experience with Chai-Webdriver for Selenium.
I added await to the assertion and it fixed the issue:
Example using Cucumberjs:
Then(/I see heading with the text of Tasks/, async function() {
await chai.expect('h1').dom.to.contain.text('Tasks');
});
Just a heads-up that you can get a UnhandledPromiseRejectionWarning if you accidentally put your test code outside of the it-function. 😬
describe('My Test', () => {
context('My Context', () => {
it('should test something', () => {})
const result = testSomething()
assert.isOk(result)
})
})

Categories