Promise.all causes Jest to display UnhandledPromiseRejectionWarning - javascript

I have some code which calls Promise.all. It runs OK in the browser with no warnings in the console.
There are 3 functions f1, f2 & f3 all of which return a promise. The code looks like this
Promise.all([
f1(),
f2(),
f3()
]).then((values) => {
resolve({success: true})
}).catch(err => {
reject(err)
})
When I use Jest to test the file containing the above code I see this error.
(node:17177) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 18)
Is this the wrong way to code the above or is it a bug within Jest?
Here's the actual code that I'm using:
getDataFromDatabase() {
return new Promise((resolve, reject) => {
const assessmentUrl = `${this.assessmentUrl}`
http.get(assessmentUrl).then(response => {
if (response.data.record === null) {
Promise.all([
this._getPupilPlacement(),
this._getSurveyQuestions(),
this._getCompetencies()
]).then((values) => {
successState.pupilPlacement = values[0].pupilPlacement
successState.items = values[1].items
successState.formid = values[2].formid
successState.competencies = values[3].competencies
const panels = this.getPanels(values[3].competencies)
successState.panels = panels
successState.numPages = panels.length
successState.itemsAreOverridden = true
resolve(successState)
}).catch(err => {
reject(err)
})
}
else {
resolve(response.data.record)
}
})
})
}

Avoid the Promise constructor antipattern! You were forgetting to handle errors from the http.get(assessmentUrl) promise.
You should be writing
getDataFromDatabase() {
const assessmentUrl = `${this.assessmentUrl}`
return http.get(assessmentUrl).then(response => {
//^^^^^^
if (response.data.record !== null)
return response.data.record;
return Promise.all([
// ^^^^^^
this._getPupilPlacement(),
this._getSurveyQuestions(),
this._getCompetencies()
]).then(values => {
const panels = this.getPanels(values[3].competencies)
return {
// ^^^^^^
pupilPlacement: values[0].pupilPlacement,
items: values[1].items,
formid: values[2].formid,
competencies: values[3].competencies,
panels: panels,
numPages: panels.length,
itemsAreOverridden: true,
};
});
});
}

Explanation:
Calling reject will throw an error. If your top level promise doesn't catch it, then well it's an unhandled promise.
MDN Image src
Solution:
getDataFromDatabase().catch(err=>console.lor(err.message));
Example of a promise that rejects.:
function getDataFromDatabase(){
return Promise.reject(123);
}
getDataFromDatabase()
.then(data=>console.log("Success " + data))
.catch(err=>console.log("Error " + err));
Promise MDN doc
Future recommendation:
For every child promise you seem to be adding a .catch() which isn't needed. As long as somewhere higher up there is a catch, then the promise will be handled.

Related

async in map with callback won't work file be still undefined

hello all I have some problems with my async function the test will be undefined what am I doing wrong I need help with this it's so frustrating
async function fileToObj(jsonOfXls){
const promises = jsonOfXls.Blad1.map(async x => {
let test;
await base64.encode(`pdfs/${x.E}`, function (err, base64String) {
test = base64String
})
return { gtin: x.D, gln: x.C, order: x.B, file: test }
})
const output = await Promise.all(promises)
console.log(output)
}
i try now this :
async function fileToObj(jsonOfXls) {
const output = await Promise.all(
jsonOfXls.Blad1.map(async x => {
const file = await new Promise((resolve, reject) => {
base64.encode(`pdfs/${x.E}`, function(err, base64String) {
if (err != null) {
return reject(err)
}
resolve(base64String)
})
})
return { gtin: x.D, gln: x.C, order: x.B, file }
})
)
console.log(output)
}
but i get this error:
72) UnhandledPromiseRejectionWarning: encode fail
(node:8772) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (reject
ion id: 1)
You can only usefully await a promise.
base64.encode takes a callback which implies it doesn't return a promise.
Awaiting its return value therefore has no practical effect.
You would need to wrap it in a promise before you can await it.

How to wrap a Javascript function so all errors are caught including Promise rejections

I want to write a function used to wrap other functions so all errors are caught, including errors generated by a Promise rejection (which normally requires the .catch Promise method).
The goal is to be able to wrap functions so all runtime errors are handled. An example usage is a function we want to run, but that is optional and not part of the core business flow. If there is an error, we want to report it and later fix it, but we do not want it to stop program flow.
It should be able to wrap functions with any number of arguments, and return the same value as the original function, including if the original function returns a promise.
Am I handling all possible cases below? Is there a simpler way to do this?
const catchAllErrors = (fn) => (...args) => {
try {
const possiblePromise = fn(...args);
// Is it a promise type object? Can't use "instanceof Promise" because just
// for example, Bluebird promise library is not an instance of Promise.
if (typeof possiblePromise.catch === 'function') {
return Promise.resolve(possiblePromise).catch((error) => {
console.log('Caught promise error.', error);
});
}
return possiblePromise;
} catch (error) {
console.log('Caught error.', error);
}
};
// EXAMPLE USAGE
// Applying the wrapper to various types of functions:
const throwsErr = catchAllErrors((x, y) => {
throw `Error 1 with args ${x}, ${y}.`;
});
const promiseErr = catchAllErrors((a, b) => Promise.reject(`Error 2 with args ${a}, ${b}.`));
const noError = catchAllErrors((name) => `Hi there ${name}.`);
const noErrorPromise = catchAllErrors((wish) => Promise.resolve(`I wish for ${wish}.`));
// Running the wrapped functions:
console.log(throwsErr(1, 2));
promiseErr(3, 4).then((result) => console.log(result));
console.log(noError('folks'));
noErrorPromise('sun').then((result) => console.log(result));
Don't try to detect whether something is a promise or not yourself. Use the builtin thenable detection of promise resolution. You can use either the Promise constructor to catch the exception:
const catchAllErrors = (fn) => (...args) => {
return new Promise(resolve => {
resolve(fn(...args));
}).catch((error) => {
console.log('Caught error.', error);
});
};
or just go for async/await syntax:
const catchAllErrors = (fn) => async (...args) => {
try {
return await fn(...args);
} catch (error) {
console.log('Caught error.', error);
}
};
(If you used Bluebird anyway, you could also call its Promise.try method for this purpose)

Promise rejection throws warning even if it's being caught later [duplicate]

This question already has answers here:
Prevent "Unhandled promise rejection" error
(3 answers)
Closed 4 years ago.
Example
class Foo {
private pro = new Promise(() => {
throw new Error();
});
public usePro() {
return this.pro.then(() => {});
}
}
let foo = new Foo();
setTimeout(() => {
foo.usePro().then(() => {
console.log("end.");
}).catch(() => {
console.log("error.");
})
}, 1000);
I understand that javascript can't know at runtime that someone will catch the error later, so how am I suppose to do in such a situation ?
Console
(node:39166) UnhandledPromiseRejectionWarning: error
(node:39166) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:39166) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
error.
(node:39166) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
Errors should be caught wherever the Promise is used, even if that Promise is returned (and caught) by something else later. One option would be to assign to this.proValue a resolved object or a rejected object, depending on whether the original Promise resolves or rejects. Then, when usePro is called, check this.proValue and return either Promise.resolve(resolved) or Promise.reject(rejected). Using standard Javascript so this can be shown in a runnable snippet:
class Foo {
constructor() {
this.pro = new Promise(() => {
throw new Error('Problem!');
})
.then((resolved) => {
this.proValue = { resolved };
})
.catch((rejected) => {
this.proValue = { rejected };
});
}
usePro() {
const { resolved, rejected } = this.proValue;
if (resolved) return Promise.resolve(resolved);
else if (rejected) return Promise.reject(rejected);
}
}
const foo = new Foo();
setTimeout(() => {
foo.usePro().then(() => {
console.log("end.");
}).catch((e) => {
console.log("error caught. " + e);
})
}, 1000);
If you want to be able to call usePro before Foo's internal Promise has resolved (or rejected), then when usePro is called, construct and return a Promise that resolves once this.pro's Promise resolves (or rejects). unfortunately the code required is moderately more complicated:
class Foo {
constructor() {
this.callProms = [];
setTimeout(() => {
this.pro = new Promise(() => {
throw new Error('Problem!');
})
.then((resolved) => {
this.proValue = { resolved };
})
.catch((rejected) => {
this.proValue = { rejected };
})
.finally(() => {
console.log('internal promise finishing');
this.resolveCalls();
});
}, 1000);
}
resolveCalls() {
this.callProms.forEach((resolve) => {
resolve(this.getProValue());
});
}
getProValue() {
const { proValue } = this;
if (!proValue) return;
const { resolved, rejected } = proValue;
if (resolved) return Promise.resolve(resolved);
else if (rejected) return Promise.reject(rejected);
}
usePro() {
return this.getProValue()
|| new Promise((resolve) => {
this.callProms.push(resolve);
});
}
}
console.log('Starting');
const foo = new Foo();
// Immediate call of `usePro`:
foo.usePro().then(() => {
console.log("end.");
}).catch((e) => {
console.log("immediate error caught. " + e);
})
// Delayed call:
setTimeout(() => {
foo.usePro().then(() => {
console.log("end.");
}).catch((e) => {
console.log("delayed error caught. " + e);
})
}, 2000);
Great answer by CertainPerformance.
Let me add that in Node.js, you can also add an unhandledRejection listener on process:
process.on('unhandledRejection', reason => {
console.error({Error:reason})
process.exit(1);
});
You could use Promise.all to delay the resolving:
const delay = ms => new Promise(res => setTimeout(res, ms));
Promise.all([
foo.usePro(),
delay(1000)
]).then(() => {
console.log("end.");
}).catch(() => {
console.log("error.");
});
That way the .catch is directly attached, but the then callback is executed after the delay.

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.

Unhandled rejection Error. Even though tests are passing

I'm testing this function:
UserController.prototype.getDashboard = function getDashboard(req, res, next) {
let pages, user;
return this.User.findById(req.user.id)
.populate('club')
.execAsync()
.then(dbUser => {
user = dbUser;
FB.setAccessToken(user.token);
return FB.getAsync('me/accounts')
})
.then(fbPages => {
pages = fbPages.data;
return Promise.all(pages.map(page => {
return FB.getAsync(`${page.id}/events`)
.then(events => events)
.catch(e => next(Boom.wrap(e)));
}))
})
.then(events => {
let entity = {
user,
pages: _.map(pages, (page, i) => _.assign({}, page, { events: events[i].data }))
};
return res.send(entity);
})
.catch(err => next(Boom.wrap(err)));
};
But when I test it, even though it passes, I get Unhandled rejection Error
This is my test:
it('handles errors', (done) => {
let mockError = new Error('test-error');
let mockRequest = { user: { id: mockUser._id }};
let mockNext = td.function();
let capture = td.matchers.captor();
td.replace(FB, 'getAsync');
td.when(FB.getAsync('me/accounts'))
.thenReturn(Promise.resolve(usersTestData.getDashboard.pages));
td.when(FB.getAsync(`1769754093273789/events`))
.thenReturn(Promise.resolve(usersTestData.getDashboard.events[0]))
// Promise rejection was handled asynchronously
td.when(FB.getAsync(`731033223716085/events`))
.thenReturn(Promise.reject(mockError))
td.when(mockNext(Boom.wrap(mockError)))
.thenDo(() => { done() });
controller.getDashboard(mockRequest, _.noop, mockNext);
});
The weird part, is that it passes. So the mockNext is being called with what I expected but I get a log in the console with the unhandled rejection. I've tried to handle .catch for each value of the mapping, but it still shows the same error (warning?).
I'm using Bluebird promises, mongoose(promisified) and testdouble. BTW this is the log from the console afte running the test:
dashBoard
Unhandled rejection Error: test-error
✓ handles errors
After a lot of trial and error I found what was going on. The problem had to do with testdouble API .thenReturn().
The error appeared if .thenReturn(Promise.reject(...)) I configured td to use Bluebird promises and used their API for promises .thenReject(...). The result test code would be:
td.config({ promiseConstructor: require('bluebird') });
it('handles errors', (done) => {
let mockError = new Error('test-error');
let mockRequest = { user: { id: mockUser._id }};
let mockNext = td.function();
let capture = td.matchers.captor();
td.replace(FB, 'getAsync');
td.when(FB.getAsync('me/accounts'))
.thenReturn(Promise.resolve(usersTestData.getDashboard.pages));
td.when(FB.getAsync(`1769754093273789/events`))
.thenResolve(usersTestData.getDashboard.events[0])
td.when(FB.getAsync(`731033223716085/events`))
.thenReject(mockError)
td.when(mockNext(Boom.wrap(mockError)))
.thenDo(() => { done() });
controller.getDashboard(mockRequest, _.noop, mockNext);
});
This test would work with no warnings.
I guess the problem is with
td.when(FB.getAsync(`731033223716085/events`))
.thenReturn(Promise.reject(mockError))
in case the getAsync function is never called and the rejected promise is never used (and no error handler is chained to it). You can tell it to ignore this by adding a handler yourself however:
function ignore(e) {}
function ignoredRejection(e) {
var p = Promise.reject(e);
p.catch(ignore);
return p;
}
td.when(FB.getAsync(`731033223716085/events`))
.thenReturn(ignoredRejection(mockError))

Categories