I'm having a hard time trying to understand how I can make this function work. I think it stems from me not really understanding 100% how to handle promises, but I've read all the tutorials I can find and I still don't see how I can do this.
The following is a function, which calls an endpoint on an API. When it's successful, it'll return a token for use in future requests. When it fails, I want to throw an exception and completely stop the execution.
I've heard that when throwing in a catch handler like this, then you're thrown exception is handled by a hidden try/catch and this passes to the next catch handler. But this is effectively meaning my exception gets black-holed. Which I've heard is a thing and I've read other questions on this website about that.
The actual example isn't this trivial. But the effective code is still the same, a try/catch wrapped around all the code in question. But any exceptions never land in the right place. Then lambda fails with the exception in question. But it's not handled by the code that I wrote, so the response is wrong. I need to handle the code and output a different reply based on what exception is being thrown. So I change it around, fix up the data, add other related information and return that. But since I'm never landing in the right place. I can never do that.
How could I alter this function, so that it correctly lands in the exception handler I've given? Thanks for your help
let getToken = async () => {
let transport = axios.create({ baseURL, headers: passwordHeaders });
return transport.post('/authorization/login')
.then(response => {
let accessToken = _.get(response, 'data.access_token', null);
if(accessToken !== null){
let base64 = Buffer.from(accessToken, 'base64');
let token = base64.toString('ascii');
return token.split(";").shift().split(":").pop();
}
throw new LoginError(response.statusText, response.status));
})
.catch(error => {
if(_.get(error, 'response.status', 'null') === 403){
throw new UnauthorizedError(error.response.statusText);
}
throw error;
});
}
try{
let token = await getToken();
}catch(error){
console.log("this exception should land here");
}
As mentioned in the problems the main problem is the mix of then style and async/await. If we lean into the async approach we could do the following:
let getToken = async () => {
let transport = axios.create({ baseURL });
try {
const response = await transport.post("/authorization/login");
let accessToken = _.get(response, "data.access_token", null);
if (accessToken !== null) {
let base64 = Buffer.from(accessToken, "base64");
let token = base64.toString("ascii");
return token.split(";").shift().split(":").pop();
}
} catch (e) {
throw e;
}
};
try {
let token = await getToken();
} catch (error) {
console.log("this exception should land here");
}
As getToken is an asynchronous function it returns a Promise, returning within this function equals to using Promise.resolve and throwing equals to Promise.reject. In my case the catch block of getToken does not do anything and getToken gets called from within a try / catch. We might want to delegate error handling to the outer try catch by removing the inner one.
I wrote a Javascript code using async, await and used try, catch in each async function.
Suppose if I write 10 async function then I need to write try, catch in all those 10 functions. Now I am getting question in my mind that doesn't it cause performance overhead?
If it causes any performance overhead then what will be the solution for this problem?
You can write single try catch, in try you can add all of the async-await functions.
try {
// service as an example
const get = await service.get()
const post = await service.post()
const delete = await service.delete()
} catch(e) {
throw new Error(e)
}
I was trying to mock rejected value and got this error. It's weird that this construction works in the case of "success" addUser.mockImplementation(value => jest.fn().mockResolvedValue(value)), but when I'm trying to do the same trick with rejecting, it doesn't work and says 'Cannot read property 'createEvent' of null'
Here is my test case
it('receives invalid value and throws an error', async () => {
addUser.mockImplementation(() =>
jest.fn().mockRejectedValue(new Error('Sample error'))
)
const enqueueSnackbar = jest.fn()
useSnackbar.mockReturnValue({ enqueueSnackbar })
const { emailInput, form, submitButton } = setup()
await act(async () => {
fillIn(emailInput, 'sample#mail.com')
})
expect(emailInput.value).toBe('sample#mail.com')
expect(submitButton).toHaveProperty('disabled', false)
await act(async () => {
fireEvent.submit(form)
})
expect(enqueueSnackbar).toHaveBeenCalledTimes(1)
expect(enqueueSnackbar).toHaveBeenCalledWith(`Sample error`, {
variant: 'error'
})})
Does anyone know how to make it work?
This seems to be the #1 question that is found when someone Googles "Cannot read property 'createEvent' of null", so leaving this answer here for those readers:
For me this error came in the midst of a test.
When executing a series of tests, some test or the other used to fail with this error, with no indication of where things went wrong. But the answer turned out to be not the test but the component itself:
It was an unmocked API call.
There was an API call being made in a hook and that hook was used in the component with the failing tests. Obviously Jest cleaned up everything after completing its test, and when the call returned, it found nothing so it errored out.
Mocking the hook solved the issue.
If someone comes across such an error, make sure to mock any asynchronous logic you have, especially if it interacts with the DOM when it returns.
Similar to what #alexandre_anicio stated. I was getting this error when using the findAllByText query.
expect(screen.findAllByText('word'))...
When I switched to the getAllByText the error went away and the test passed.
expect(screen.getAllByText('word'))...
If I used expect(await screen.findAllByText('word'))... I noticed the test passed as well.
Digging deeper, this is because findBy tests return a promise so the await is needed. https://testing-library.com/docs/guide-disappearance/#1-using-findby-queries
It would have been nice for the library to throw a better error however.
This seems to work for me but I can't explain it. Try removing your act() wrapper, and use await immediately after calling the fireEvent function.
fireEvent.submit(form);
await wait();
When I encountered this same error message, I discovered I had forgotten to declare my test function as async after I updated the expectation to include await.
waitFor already uses act under the hood so there's no need to use the act blocks there.
I recognize the error you mentioned but the way I replicate it is using waitFor without await, something like this:
it('works', async() => {
render(<SomeComponent />);
// (some setup here)
waitFor(() => { // notice that we are not awaiting this promise
expect(someChange).toBeInTheDocument();
});
});
Could you try
it('receives invalid value and throws an error', async () => {
addUser.mockImplementation(() =>
jest.fn().mockRejectedValue(new Error('Sample error'))
)
const enqueueSnackbar = jest.fn()
useSnackbar.mockReturnValue({ enqueueSnackbar })
const { emailInput, form, submitButton } = setup()
fillIn(emailInput, 'sample#mail.com') // This is using some fireEvent under the hood right?
await waitFor(() => {
expect(emailInput.value).toBe('sample#mail.com')
expect(submitButton).toHaveProperty('disabled', false)
});
fireEvent.submit(form)
await waitFor(() => {
expect(enqueueSnackbar).toHaveBeenCalledTimes(1)
expect(enqueueSnackbar).toHaveBeenCalledWith(`Sample error`, {
variant: 'error'
})
});
})
Similar issue and error messages, adding await before userEvent did the trick
Before
userEvent.upload(screen.getByRole('button'), ...)
userEvent.upload(screen.getByLabelText('Upload'), FILE)
After
await userEvent.upload(screen.getByRole('button'), ...)
await userEvent.upload(screen.getByLabelText('Upload'), FILE)
I had some problems using mockImplementation(() => Promise) (returning some promise) and the await waitFor(()=> ...) at the same time.
If you are using react-testing-library, you can work around this problem using findBy query, that are a combination of getBy queries and waitFor. The only downside is that you have to find something visual (a text, data-test-id, label, etc...) that can tell you that the mock function have been called. On your code you can try something like this:
it('receives invalid value and throws an error', async () => {
addUser.mockImplementation(() =>
jest.fn().mockRejectedValue(new Error('Sample error'))
)
await screen.findByText('Sample Error message reflected in your component')
... rest of your tests ...
})
1. await waitFor(() => expect(history.location.pathname).toBe('/auth'))
2. await waitFor(() => expect(history.location.pathname)).toBe('/auth')
It's about something else but the same error. Spent 2 hours so you don't have to :)
The second one with the parenthesis in the wrong place was the culprit
I was getting
/…/node_modules/.pnpm/react-dom#17.0.2_react#17.0.2/node_modules/react-dom/cjs/react-dom.development.js:3905
var evt = document.createEvent('Event');
^
TypeError: Cannot read property 'createEvent' of null
at Object.invokeGuardedCallbackDev (/…/node_modules/.pnpm/react-dom#17.0.2_react#17.0.2/node_modules/react-dom/cjs/react-dom.development.js:3905:26)
at invokeGuardedCallback (/…/node_modules/.pnpm/react-dom#17.0.2_react#17.0.2/node_modules/react-dom/cjs/react-dom.development.js:4056:31)
at flushPassiveEffectsImpl (/…/node_modules/.pnpm/react-dom#17.0.2_react#17.0.2/node_modules/react-dom/cjs/react-dom.development.js:23543:11)
at unstable_runWithPriority (/…/node_modules/.pnpm/scheduler#0.20.2/node_modules/scheduler/cjs/scheduler.development.js:468:12)
at runWithPriority$1 (/…/node_modules/.pnpm/react-dom#17.0.2_react#17.0.2/node_modules/react-dom/cjs/react-dom.development.js:11276:10)
at flushPassiveEffects (/…/node_modules/.pnpm/react-dom#17.0.2_react#17.0.2/node_modules/react-dom/cjs/react-dom.development.js:23447:14)
at Object.<anonymous>.flushWork (/…/node_modules/.pnpm/react-dom#17.0.2_react#17.0.2/node_modules/react-dom/cjs/react-dom-test-utils.development.js:992:10)
at Immediate.<anonymous> (/…/node_modules/.pnpm/react-dom#17.0.2_react#17.0.2/node_modules/react-dom/cjs/react-dom-test-utils.development.js:1003:11)
at processImmediate (internal/timers.js:461:21)
Tracked down to an import statement.
I was able to "fix" it by changing:
import { AddCircle, RemoveCircle } from '#mui/icons-material';
to
import AddCircle from '#mui/icons-material/AddCircle';
import RemoveCircle from '#mui/icons-material/RemoveCircle';
Crazy.
If the error is because of wrong usage of jest's findBy* instead of async/await you can also return promise:
it('test', () => {
expect.assertions(1);
return screen
.findByTestId('iiError')
.then(elem =>
expect(elem).toHaveTextContent(
"This is error message"
)
);
});
Do not forget about expect.assertions and return!
Ref: https://jestjs.io/docs/tutorial-async
I had the same issue, the culprit in my case was that the rendered React component was unmounted with .unmount(). A running API call triggered a callback and React tried to update the DOM, that was already unmounted.
Since this is the top result on Google for this issue, I'll add my own answer. For me, this issue was happening on Circle CI when my tests were running. The Circle CI server ran the tests more slowly because it's CPU is limited, so a long-running test with lots of userEvent.types in it was exceeding the default timeout of 5 seconds.
I don't know why it didn't give an error about exceeding the timeout, but this thread helped me track it down.
All I had to do was increase the timeout on the long-running test.
Error occurred for me because I had work scheduled from useEffect that resolved after the rest was torn down.
The solution is to await Promise.sleep() after each test.
I was facing the same issue
It had to something with the async function not completing before the test case completes
I solved this using await flushMicrotasksQueue()
in my code
I am using knex npm version 0.15.2. while Rollback the transaction I'm getting the following error:
Error: Transaction rejected with non-error: undefined
Trx.rollback()
above function used for rollback.
Same code working for knex version 0.12.6
This is the function I used for commit/Rollback.
function Commit(pTrx, pIsCommit, pCallback) {
try {
var co = require("co");
var q = require('q');
var Q = q.defer();
co(function* () {
if (pIsCommit) {
yield pTrx.commit();
} else {
yield pTrx.rollback();
}
Q.resolve(pCallback('SUCCESS'));
}).catch(function (error) {
Q.reject(pCallback(error));
});
return Q.promise;
} catch (error) {
console.log(error)
}
}
This code could use some work. :) Here are a few things that pop out:
You don't need co or q anymore. Promises and async/await are built-in and much simpler to use. Async functions automatically return promises that will be resolved with the value returned or rejected if an error is thrown. Learn about async/await here: https://jsao.io/2017/07/how-to-get-use-and-close-a-db-connection-using-async-functions/
You shouldn't return success as a string. If the function completes without throwing an exception, then success is implied. I see people do this from time to time, but it's often for the wrong reasons.
You shouldn't accept callback in a function that returns a promise, it should be one or the other. The caller of your function will either pass a callback or await it's completion.
If you are going to use callbacks, then you should return null as the first parameter when successful. See the last sentence here: https://nodejs.org/en/knowledge/getting-started/control-flow/what-are-callbacks/
Your function name, Commit, starts with an uppercase. This convention is typically used to indicate that the function is a contstructor function and meant to be invoked with the new keyword.
Here's how the function could look once cleaned up:
async function commit(pTrx, pIsCommit) {
// Not using a try/catch. If an error is thrown the promise returned will be rejected.
if (pIsCommit) {
await pTrx.commit();
} else {
await pTrx.rollback();
}
// Not going to return anything. If we get to this point then success is implied when the promise is resolved.
}
A consumer of your function would call it with something like:
async function myWork() {
// do work; get pTrx
try {
await commit(pTrx, true);
// If I get here, then I can assume commit was successful, no need to check a return value of 'SUCCESS'
} catch (err) {
// handle error
}
}
It's hard to say where the issue is with the code in its current state. However, if the issue truly is with Knex, then you should probably post this as an issue in the Knex repo: https://github.com/tgriesser/knex/issues But you should write a reproducible test case that proves its an issue with Knex.
After deployment my array does not seem to be defined and I get a "forEach is not a function" error:
textPayload: "TypeError: currencies.forEach is not a function
at currenciesMenuConfiguration (/srv/menu/currencies-menu.js:22:16)
Here's the code for the currencies array:
async function currenciesMenuConfiguration() {
currencies = await getCol("currencies")
currencies.forEach(element => {
return currenciesList[element[Object.keys(element)].id] = element[Object.keys(element)].current
});
}
This gets called right after being defined with currenciesMenuConfiguration()
getCol is defined as:
// Get all documents in collection from Firestore
async function getCol(colName) {
return fs.collection(colName).get()
.then(res => {
return res.docs.map(doc => {
var rObj = {};
rObj[doc.id] = doc.data();
return rObj;
})
})
.catch(err => {
return `Error getting collection: ${err}`;
});
}
As mentioned on firebase serve locally this works without an issue. After deployment, I get this error. Am I missing something about asynchronous code in the cloud?
Also, any tips on how I would go about debugging this myself would be much appreciated. #BeginngersLife.
// I have checked the questions on so about firebase serve vs deployment but they mostly deal with issues around deployment or don't address the issue I am facing. One question that might help me get further in this is: firebase serve and debugging functions?.
See comment below question: I did indeed not return an array but an object. Closing. Thanks for the hint #Frank van Puffelen.