I'm struggling with a loop of async await function. I'm creating an API which POST a request to another API. I have to make a loop because users could insert many items at once in the database.
The API that I call rejects Parallel executions so I can't use Promise.all :/
In order to implement that service I made a function to insert one object :
const addValue = async (ID:number, cookie: string) => {
try {
const addValueBody = await addValueQuery(ID:number)
const header = { 'cookie': cookie, 'content-type': 'application/json' }
const fetchResult = fetch('http://api-to-call.xyz', { method: `POST`, headers: header, body: addValueBody })
const response = await fetchResult
const jsonData = await response.json()
return jsonData
} catch (e) {
throw Error(e.message)
}
}
And a function which will execute addValue inside a for loop :
const addMulti = async (values: any, cookie: string) => {
for (const item of values) {
await addValue(item.ID, cookie)
}
}
The problem is that when I call addMulti and if there is an Error I have a UnhandledPromiseRejectionWarning.
Also I tried to put de for ... in or await addValue inside a try catch but it doesn't catch the Error(s)
Hence, I have 2 questions :
How can I throw en Error if it occurs during the execution of the loop ?
Is it possible to break the loop if there is an error ?
Thanks for your help :)
How can I throw en Error if it occurs during the execution of the loop ? Is it possible to break the loop if there is an error ?
Your code does that already, by awaiting addValue. If addValue throws an error, since addMulti doesn't catch, it terminates addMulti as well.
The problem is that when I call addMulti and if there is an Error I have a UnhandledPromiseRejectionWarning.
The code you've shown is correct¹, the problem is that it appears you have no error handling on the call to addMulti. That code must either:
Handle errors from it (via try/catch in an async function if you don't want to allow it to propagate, or via the catch method if using the promise directly), or
Propagate the error to something that will handle it (by not catching it in an async function, or by returning the promise in a non-async function)
So for instance, if the call to addMulti is in a non-async function and if nothing calling that function will handle errors, you need to handle it at that stage:
addMulti(/*...*/)
.catch(error => {
// ...handle/report the error
});
If it's in an async function but nothing handles that async function's errors, then:
try {
await addMulti(/*...*/);
} catch (e) {
// ...handle/report the error
}
¹ ...other than it seems odd to catch e and then do throw Error(e.message); just let the error propagate.
When you get UnhandledPromiseRejection working with async functions, it almost surely means that you didn't catch the thrown error. In your case you could just wrap the whole loop in try{...} catch{...} block:
const addMulti = async (values: any, cookie: string) => {
try {
for (const item of values) {
await addValue(item.ID, cookie)
}
} catch(e) {
console.log('Error occured in async function')
}
}
Related
I'm writing an async test that expects the async function to throw like this:
it("expects to have failed", async () => {
let getBadResults = async () => {
await failingAsyncTest()
}
expect(await getBadResults()).toThrow()
})
But jest is just failing instead of passing the test:
FAIL src/failing-test.spec.js
● expects to have failed
Failed: I should fail!
If I rewrite the test to looks like this:
expect(async () => {
await failingAsyncTest()
}).toThrow()
I get this error instead of a passing test:
expect(function).toThrow(undefined)
Expected the function to throw an error.
But it didn't throw anything.
You can test your async function like this:
it('should test async errors', async () => {
await expect(failingAsyncTest())
.rejects
.toThrow('I should fail');
});
'I should fail' string will match any part of the error thrown.
I'd like to just add on to this and say that the function you're testing must throw an actual Error object throw new Error(...). Jest does not seem to recognize if you just throw an expression like throw 'An error occurred!'.
await expect(async () => {
await someAsyncFunction(someParams);
}).rejects.toThrowError("Some error message");
We must wrap the code in a function to catch the error. Here we are expecting the Error message thrown from someAsyncFunction should be equal to "Some error message". We can call the exception handler also
await expect(async () => {
await someAsyncFunction(someParams);
}).rejects.toThrowError(new InvalidArgumentError("Some error message"));
Read more https://jestjs.io/docs/expect#tothrowerror
Custom Error Class
The use of rejects.toThrow will not work for you. Instead, you can combine the rejects method with the toBeInstanceOf matcher to match the custom error that has been thrown.
Example
it("should test async errors", async () => {
await expect(asyncFunctionWithCustomError()).rejects.toBeInstanceOf(
CustomError
)
})
To be able to make many tests conditions without having to resolve the promise every time, this will also work:
it('throws an error when it is not possible to create an user', async () => {
const throwingFunction = () => createUser(createUserPayload)
// This is what prevents the test to succeed when the promise is resolved and not rejected
expect.assertions(3)
await throwingFunction().catch(error => {
expect(error).toBeInstanceOf(Error)
expect(error.message).toMatch(new RegExp('Could not create user'))
expect(error).toMatchObject({
details: new RegExp('Invalid payload provided'),
})
})
})
I've been testing for Firebase cloud functions and this is what I came up with:
test("It should test async on failing cloud functions calls", async () => {
await expect(async ()=> {
await failingCloudFunction(params)
})
.rejects
.toThrow("Invalid type"); // This is the value for my specific error
});
This is built on top of lisandro's answer.
If you want to test that an async function does NOT throw:
it('async function does not throw', async () => {
await expect(hopefullyDoesntThrow()).resolves.not.toThrow();
});
The above test will pass regardless of the value returned, even if undefined.
Keep in mind that if an async function throws an Error, its really coming back as a Promise Rejection in Node, not an error (thats why if you don't have try/catch blocks you will get an UnhandledPromiseRejectionWarning, slightly different than an error). So, like others have said, that is why you use either:
.rejects and .resolves methods, or a
try/catch block within your tests.
Reference:
https://jestjs.io/docs/asynchronous#asyncawait
This worked for me
it("expects to have failed", async () => {
let getBadResults = async () => {
await failingAsyncTest()
}
expect(getBadResults()).reject.toMatch('foo')
// or in my case
expect(getBadResults()).reject.toMatchObject({ message: 'foo' })
})
You can do like below if you want to use the try/catch method inside the test case.
test("some test case name with success", async () => {
let response = null;
let failure = null;
// Before calling the method, make sure someAsyncFunction should be succeeded
try {
response = await someAsyncFunction();
} catch(err) {
error = err;
}
expect(response).toEqual(SOME_MOCK_RESPONSE)
expect(error).toBeNull();
})
test("some test case name with failure", async () => {
let response = null;
let error = null;
// Before calling the method, make sure someAsyncFunction should throw some error by mocking in proper way
try {
response = await someAsyncFunction();
} catch(err) {
error = err;
}
expect(response).toBeNull();
expect(error).toEqual(YOUR_MOCK_ERROR)
})
Edit:
As my given solution is not taking the advantage of inbuilt jest tests with the throwing feature, please do follow the other solution suggested by #Lisandro https://stackoverflow.com/a/47887098/8988448
it('should test async errors', async () => {
await expect(failingAsyncTest())
.rejects
.toThrow('I should fail');
});
test("It should test async on failing cloud functions calls", async () => {
failingCloudFunction(params).catch(e => {
expect(e.message).toBe('Invalid type')
})
});
I'd like to catch all my exceptions in one place, but I can't do that currently:
There is an important thing to note if you like more try/catch. The following code won't catch the error:
[...]
Remember: a rejected Promise will propagate up in the stack unless you catch it. To catch the error properly in try/catch you would refactor like so:
whatever().catch(err => console.error(err));
But here is my code as I would like to have it:
async function getMtgJsonVersion() {
try {
const response = await axios(metaUrl).catch((err) => { throw err; });
const { data: { meta: { version } } } = response;
return version;
} catch (error) {
console.error(`Could not fetch MTG JSON metadata: ${error}`);
throw error;
}
}
and my test:
// console.error is mocked higher up in the test file
it.only('handles errors in fetching', async () => {
expect.assertions(2);
axios.mockReset(); // I use mockImplementationOnce elsewhere
axios.mockRejectedValueOnce(new Error('bang'));
expect(() => getMtgJsonVersion()).rejects.toThrow('bang');
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('bang'));
});
But when I run it, I get that the last expect is not fulfilled?
expect(jest.fn()).toHaveBeenCalledWith(...expected)
Expected: StringContaining "bang"
Number of calls: 0
I was hoping to catch all my throws in one place, but it looks like it's not as simple as I thought.
Is this possible, and how?
Because expect(fn).rejects.* is an asynchronous action, then it will take "a little time" to finish.
In your code, expect(console.error).toHaveBeenCalledWith(expect.stringContaining('bang')) will run before expect(() => getMtgJsonVersion()).rejects.toThrow('bang'); line. At that time, the console.log is not be called yet.
To make it work as your expectation, you have to wait until getMtgJsonVersion finishes, then assert on the log function. rejects.toThrow('bang') return a promise, then just wait for it with await keyword:
await expect(() => getMtgJsonVersion()).rejects.toThrow('bang');
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('bang'));
My note: Avoid using try/catch in "child" unit, just use it in the "final parent" function, if you just want to log when the http request is failed:
async function getMtgJsonVersion() {
const { data } = await axios(metaUrl).catch((error) => {
console.error(`Could not fetch MTG JSON metadata: ${error}`);
throw error;
});
return data.meta.version.
}
I'm making an API request using the async/await pattern in a try/catch block..
async myRequest(data) {
try {
await api.post('/my-endpoint/', data).then((response) => {
console.log(response.data)
});
} catch (ex) {
// WANT TO READ RESPONSE DATA HERE
}
}
If the request succeeds without errors, I am able to read the response with the .then() method.
If the request fails, this API returns a 422 code that triggers the try/catch exception.
However, if the request fails, this API still returns some data in the response body that I would like to to read but unable to because catch is triggered and .then() is never run.
How can I get the response body from the async function within the catch block?
Try this if you are on Axios lib:
catch (e) {
console.log(e.response.data);
}
console.log uses the toString method to format Error objects so it is hard to find the response property if you only log e (error).
Docs: https://axios-http.com/docs/handling_errors
The error object is stored in ex so you can log the error with
} catch (ex) { console.log(ex) }
One of the problems you are making is putting the .then after you call your API. When you are using the try-catch block you don't need to do the then because you can save the result into a variable.
One of the benefits of the try-catch block is that the errors are handled and you can do multiple async calls
async myRequest(data) {
try {
const response = await api.post('/my-endpoint/', data)
console.log(response.data)
} catch (ex) {
console.log(ex)
}
}
If you want to retrieve the response data from a catch block, you can do it technically.
See the following code.
const myRequest = async () => {
try {
const data = await fetch('https://jsonplaceholder.typicode.com/xxx/1').then((result)=> {
// You can change the status code to 422
if (result.status === 404) {
throw new Error('You can put your data here.')
// Make sure the argument you passed in Error is a string
}
return result.json()
})
} catch(e){
console.log(e.message);
}
}
myRequest()
I am writing a reusable fetch function (that's going to live in a separate file) and I am not too sure about the best approach to handle a function returning a null.
Say I am want to call that function and apply some logic when getOrganizationInfo doesn't return organization info. At the moment my function returns an error but falls into the first if (organization) block and I can't really handle that. I then need to use that error message to use it in the handleSubmit e.g. showError(error). What shall I do to take advantage of this else logic?
const orgName = 'ABC';
const handleSubmit = async () => {
const organization = await getOrganizationInfo(orgName);
if (organization) {
// Do something
} else {
// Do something else
}
}
Here's my function
export const getOrganizationInfo = async (
organizationName: string,
): Promise<OrganizationInfoResponse> => {
let organizationInfoResponse: OrganizationInfoResponse;
try {
const rawRes = await fetch(`/sometestendpoint/${organizationName}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
organizationInfoResponse = await rawRes.json();
if (rawRes.status >= 400) {
if (organizationInfoResponse.errorCode === ErrorCodes.INVALID_ORG_NAME) {
throw new Error('Given Organization Name is invalid');
} else {
throw new Error('Unable to get organization information.');
}
}
} catch (error) {
// organizationInfoResponse = error.toString();
throw new Error(error.toString());
}
return organizationInfoResponse;
};
The code as provided in your question will do the following when it gets into the catch block:
It executes throw new Error(error.toString())
The promise in the following expression will reject: await getOrganizationInfo(orgName)
The promise that was returned by handleSubmit will reject
None of the code in handleSubmit that follows below this await will execute
So what you claim to happen is not happening. Maybe you were talking about the version of your code where the catch block did not have that throw, but had the commented line instead:
catch (error) {
organizationInfoResponse = error.toString();
}
In that case the code will "fall into the if", because then the error is swallowed by the above catch block:
The function continues with return organizationInfoResponse
The promise in the following expression will fulfill: await getOrganizationInfo(orgName)
The function execution context of handleSubmit is restored and organization is assigned the fulfilment value (i.e. error.toString())
The if (organization) condition is truthy, and so the if block executes
Solution
To get the else block executed, use the throw version of your code, and either introduce a try...catch block in handleSubmit, or (simpler) chain a .then and .catch call on the promise:
const handleSubmit = () => {
return getOrganizationInfo(orgName).then(organisation => {
if (!organisation) throw new Error("Organisation is falsy");
// Do something
}).catch(error => {
// Do something else
});
}
I have a simple async function. It just sends a request and returns the data:
export const updatePanorama = async ({ commit }, payload) => {
const urlEnd = '/v1/pano/update'
const type = 'post'
const resp = await api.asyncRequest(urlEnd, type, payload)
commit('SET_PANORAMA', resp.data)
return resp
}
And this is how I'm using the function:
handleUpdatePanorama (panorama) {
const payload = {}
this.updatePanorama(payload).then(resp => {
this.setIsLoading(false)
this.handleAlert('updateSuccess', 'success')
}).catch(() => {
this.setIsLoading(false)
this.handleAlert('updateError', 'danger')
})
},
The problem is, the code after catch runs if there's an error inside then. But this way I don't know whether the catch error is an request error or and error triggered by the code inside then.
I'm trying try and catch to solve that problem:
handleUpdatePanorama (panorama) {
try {
const payload = {}
const resp = await this.updatePanorama(payload)
console.log('resp:', resp)
this.setIsLoading(false)
this.handleAlert('updateSuccess', 'success')
} catch (err) {
this.setIsLoading(false)
this.handleAlert('updateError', 'danger')
})
},
However, I get an unexpected token error in this line: await this.updatePanorama(payload)
What am I doing wrong?
The problem is, the code after catch runs if there's an error inside then
The solution for that is to not use catch, but the second then parameter. Have a look at the difference between .then(…).catch(…) and .then(…, …) for details.
I'm trying try and catch to solve that problem
That won't work, the catch clause will still be called if there's an exception thrown by setIsLoading or handleAlert.
I get an unexpected token error. What am I doing wrong?
You have not declared the handleUpdatePanorama method as async.
To mitigate the issues and fix the syntax, you could write
async handleUpdatePanorama (panorama) {
var result
try {
const payload = {}
const resp = await this.updatePanorama(payload)
console.log('resp:', resp)
result = ['updateSuccess', 'success']
} catch (err) {
result = ['updateError', 'danger']
} finally {
this.setIsLoading(false)
}
this.handleAlert(...result)
},
If you need to handle errors specifically from updatePanorama, use the second argument to .then(onSuccess, onError)
handleUpdatePanorama(panorama) {
const payload = {}
this.updatePanorama(payload).then(resp => {
this.setIsLoading(false)
this.handleAlert('updateSuccess', 'success')
}, err => {
// handle error from updatePanorama
// you can throw err if you also want to handle the error in .catch()
}).catch(() => {
this.setIsLoading(false)
this.handleAlert('updateError', 'danger')
})
}
note: if you return (or have no return statement) from the error handler, any subsequent .then(onSuccess will execute, if you throw an error (or return Promise.reject() for example, then the .catch() code will also run