await not waiting for child function to execute first in react - javascript

async fetchJobs() {
this.setState({ isFetching: true }, async () => {
try{
debugger;
console.log("fetching Jobs");
var body = {
page: this.state.page,
sortBy: this.state.sortBy,
comparator: this.state.comparator,
batch: this.state.batch,
role: this.state.role,
companies: this.state.selectedCompanies
}
var job = await axios({
method: 'get',
url: `${process.env.PUBLIC_URL}/api/page_job?page=${this.state.page}`,
params: body
});
const page_jobs = job.data.page;
const jc = job.data.count;
const jobcount = parseInt(jc);
this.setState({
jobs: page_jobs,
jobcount: jobcount
}, () => {
this.getPagination();
if (this.refJobs.current)
this.refJobs.current.scrollTop = 0;
});
debugger;
console.log("fetched jobs");
}
catch(error){
console.log("err1");
throw error;
}
finally{
this.setState({ isFetching: false });
}
});
}
filterHandler = async (body) => {
this.setState({
page: 1,
sortBy: body.sortBy,
comparator: body.comparator,
batch: body.batch,
role: body.role,
selectedCompanies: body.selectedCompanies
}, async () => {
tr{
await this.fetchJobs();
console.log("not catching error");
}
catch(error){
console.log("err2");
throw error;
}
})
}
When filterHandler function is called through await it is giving output as:
fetching jobs
not catching error
fetched jobs,
instead of:
fetching jobs
fetched jobs
not catching error
I am not able to understand how to use async/await to get the desired output. As async/await should
have stopped the parent function, executed the child and then returned to the parent function.

When you await fetchJobs, you are not awaiting the fetch Promise.
Try this:
async fetchJobs() {
this.setState({ isFetching: true });
try {
// ...
}
catch(error) {
// ...
}
finally {
// ...
}
}
Another option is to explicitly generate and resolve a Promise:
fetchJobs = () => new Promise( (resolve, reject) => {
this.setState({ isFetching: true }, async () => {
try {
// ...
debugger;
console.log("fetched jobs");
resolve(jobcount); // For example...
}
catch(error) {
// ...
reject(error);
}
finally {
// Not sure if this is going to be executed, probably not
this.setState({ isFetching: false });
}
});
})

Related

How to refactor an async function with Promise inside of it

Having this async function that returns a Promise:
async function doSomething(userId) {
return new Promise(async (resolve, reject) => {
const query = 'my-query';
const roomAggregate = Room.aggregate(query).allowDiskUse(true);
Room.aggregatePaginate(roomAggregate, async function (err, data) {
if (err || !data) {
throw err;
return {
success: false,
rooms: [],
};
} else {
const rooms = [];
for (let room of data.docs) {
const roomId = room._id;
room = await computeAThing(room, {
loadWriteUps: false,
loadCreators: false,
});
room.userCompleted = await computeBThing(userId, roomId);
rooms.push(room);
}
return resolve({
rooms,
success: true,
paginator: {
// some data related to pagination
},
});
}
});
});
}
I'm not sure if it really needs to contain new Promise inside as it is already declared as async function. Is it mandatory in this case?
Because when that part was removed and at the end instead of return resolve({...}) it is only return {...} it seems to not settle.
Here is the modified code with new Promise and different return:
async function doSomething(userId) {
const query = 'my-query';
const roomAggregate = Room.aggregate(query).allowDiskUse(true);
Room.aggregatePaginate(roomAggregate, async function (err, data) {
if (err || !data) {
throw err;
return {
success: false,
rooms: [],
};
} else {
const rooms = [];
for (let room of data.docs) {
const roomId = room._id;
room = await computeAThing(room, {
loadWriteUps: false,
loadCreators: false,
});
room.userCompleted = await computeBThing(userId, roomId);
rooms.push(room);
}
return {
rooms,
success: true,
paginator: {
// some data related to pagination
},
};
}
});
}
This method is used somewhere else in this way:
const myInfo = await someObj.doSomething('myUserId');
and then checked:
if (myInfo.success) { ... }
For the first way of writing the function it works fine, for the second one myInfo is undefined and it throws and error that cannot read success of undefined.
Is something missing from the implementation?
For the second version I can see that you actually not returning anything from doSomething so I think you should do the below:
async function doSomething(userId) {
const query = 'my-query';
const roomAggregate = Room.aggregate(query).allowDiskUse(true);
const obj = Room.aggregatePaginate(roomAggregate, async function (err, data) {
if (err || !data) {
throw err;
return {
success: false,
rooms: [],
};
} else {
const rooms = [];
for (let room of data.docs) {
const roomId = room._id;
room = await computeAThing(room, {
loadWriteUps: false,
loadCreators: false,
});
room.userCompleted = await computeBThing(userId, roomId);
rooms.push(room);
}
return {
rooms,
success: true,
paginator: {
// some data related to pagination
},
};
}
});
return obj;
}
In general, you don't need to explicitly return a Promise in an async function as by default the returned value will be wrapped in a Promise , whatever it is. You just need to return something
Room.aggregatePaginat is written in coninuation-passing style, which does not interface well with promises. A generic promisify function can be used to convert any continuation-passing style function into a promise-based one. If you don't wish to write this function yourself, it is provided by Node as util.promisify -
const promisify = f => (...args) =>
new Promise((resolve, reject) =>
f(...args, (err, data) => err ? reject(err) : resolve(data))
)
Now it's easy to refactor your doSomething. Note use of Promise.all means all rooms data is processed in parallel rather than in series -
async function doSomething(userId) {
const query = 'my-query'
const roomAggregate = Room.aggregate(query).allowDiskUse(true)
const {docs:rooms} = await promisify(Room.aggregatePaginate)(roomAggregate)
return {
rooms:
await Promise.all(rooms.map(async room => ({
...await computeAThing(room, {loadWriteUps: false, loadCreators: false}),
userCompleted: await computeBThing(userId, room._id)
}))),
success: true,
paginator: ...
}
}
Also you should avoid things like { success: true } because a resolved promise is inherently "successful". A rejected one is not.
And watch out for return occurring after a throw. In this case the return is unreachable so it doesn't do what you think it's doing.
if (err || !data) {
throw err;
return { // unreachable
success: false, // these four lines
rooms: [], // are completely ignored
}; // by javascript runtime
}
Again, { success: false } goes against the Promise pattern anyway. If you want to recover from a rejection and recover with an empty list of { rooms: [] }, do it like this instead -
doSomething(user.id)
.catch(err => ({ rooms: [] }))
.then(res => console.log(res.rooms))
Better yet, you can try..catch inside doSomething and return the appropriate empty response in the event of an error. This prevents the error from bubbling up and forcing the user to handle it -
async function doSomething(userId) {
try { // try
const query = 'my-query'
const roomAggregate = Room.aggregate(query).allowDiskUse(true)
const {docs:rooms} = await promisify(Room.aggregatePaginate)(roomAggregate)
return {
rooms:
await Promise.all(rooms.map(async room => ({
...await computeAThing(room, {loadWriteUps: false, loadCreators: false}),
userCompleted: await computeBThing(userId, room._id)
}))),
paginator: ...
}
}
catch (err) { // catch
return { rooms: [] }
}
}
doSomething(user.id)
.then(res => console.log(res.rooms))

Async await in for loop

I have this function and I'm trying to push objects into the "groupData" array and then return the response object but when the function successfully runs, I get "response" as a null object. What is wrong with my code can anyone help? How can I make the function to wait for the map function to finish and then return.
const groupList = async (io, socket, userid) => {
var response = {};
try {
var groupData = [];
ddb.get({
TableName: "Tablename",
Key: { Username: userid },
})
.promise()
.then(async (user) => {
if (Object.keys(user).length === 0) {
} else {
const groups = user.Item.Chatgroups;
groups.map((g) => {
ddb.get({
TableName: "Tablename",
Key: { ChatID: g },
})
.promise()
.then(async (data) => {
groupData.push({
ChatID: g,
Chatname: data.Item.Chatname,
Group: data.Item.Group
});
})
.catch((err) => {
console.log("Chat group not found");
});
})
response["groups"] = groupData;
}
})
.catch((err) => {
response["code"] = 400;
response["message"] = "Something Went Wrong";
});
} catch (error) {
console.log(error);
} finally {
return response;
}
};
Use Promise.all and if you use async then make use of await.
Here is how your code could look. I removed the error handling -- first test this and when it works, start adding back some error handling (with try/catch):
const groupList = async (io, socket, Username) => {
const user = await ddb.get({
TableName: "Tablename",
Key: { Username },
}).promise();
if (!Object.keys(user).length) return {};
return {
groups: await Promise.all(user.Item.Chatgroups.map(async ChatID => {
const { Item: { Chatname, Group } } = await ddb.get({
TableName: "Tablename",
Key: { ChatID },
}).promise();
return { ChatID, Chatname, Group };
}))
};
};
I searched too long for this 😭
for await (item of items) {}

How to mock sns?

I faced to some issue when I wanted to test my handler which publishes a message on SNS.
Here is my code:
// my handler
export const handler = (event) => {
try {
await emitDeletionComplete(classified.metadata.classifiedRequestId);
}
catch(e) {
console.error(e);
throw new Error(e);
}
}
// my SNS service
import SNS from 'aws-sdk/clients/sns';
const snsClient = new SNS({region: process.env.AWS_REGION});
export const emitDeletionComplete = async (id) => {
try {
await snsClient.publish({
Message: JSON.stringify({
type: 'DELETE_COMPLETE',
data: {
id
}
}),
TopicArn: process.env.SNS_ARN
}).promise();
} catch(err) {
console.error(err, err.stack);
throw new Error('We do not succeed to publish the message DELETE_COMPLETE to ARN: ' + process.env.SNS_ARN);
}
};
When i want to test, i try to do :
import { handler } from '../../../src/handler/dispatch-deletion-to-legacy';
import SNS from 'aws-sdk/clients/sns';
jest.mock('aws-sdk/clients/sns', () => {
return {
__esModule: true,
default: jest.fn(() => {
return {
publish: jest.fn().mockReturnThis(),
promise: jest.fn(),
}
}),
};
});
[...]
it('should delete', () => {
let sns = new SNS();
const event = {
Records: [
{
body: JSON.stringify({...some event...})
}
]
}
handler(event);
expect(sns.publish().promise).toBeCalledTimes(1);
});
Apparently, it is never called. I don't get why.Maybe my mock is completely wrong.
i'm stuck with it for few hours now...Any idea how can I mock correctly ?
EDIT 1 : https://github.com/JLGouwy/aws-sns-mock-test
thanks
In short, your instance - let sns = new SNS(); is not instance used by your production code.
In long, you have keeping track of usage of sns mock instance, jest document
This is your example, but I changed a little. I will try to explain by comments in bellow example.
Production code:
import SNS from 'aws-sdk/clients/sns';
const snsClient = new SNS({ region: process.env.AWS_REGION });
exports.handler = async (event) => {
try {
const { classifiedRequestId } = JSON.parse(event.Records[0].body); // try to get classifiedRequestId from Record body
await emitDeletionComplete(classifiedRequestId);
} catch (e) {
console.error(e);
throw new Error(e);
}
}
const emitDeletionComplete = async (id) => { // in the same file with handler function
try {
await snsClient.publish({
Message: JSON.stringify({
type: 'DELETE_COMPLETE',
data: {
id
}
}),
TopicArn: process.env.SNS_ARN
}).promise();
} catch (err) {
console.error(err, err.stack);
throw new Error('We do not succeed to publish the message DELETE_COMPLETE to ARN: ' + process.env.SNS_ARN);
}
};
Spec file
import SNS from 'aws-sdk/clients/sns';
import { handler } from "."; // handler function in the same directory
const mockPromise = jest.fn(); // mock for deep function - promise
jest.mock('aws-sdk/clients/sns', () => {
// return a function as a constructor
return jest.fn().mockImplementation(function () { // "normal function" not arrow function
this.publish = jest.fn(() => ({ // mock publish function
promise: mockPromise, // returns an object what includes promise property
}));
});
});
describe("handler function", () => {
it("should publish delete command", async () => {
const classifiedRequestId = "12345";
const event = {
Records: [
{
body: JSON.stringify({ // input data
classifiedRequestId,
}),
}
]
};
await handler(event);
// get instance of SNS, an instance has been created in production code
const snsMocked = SNS.mock.instances[0];
// expect publish function will be call with expected parameter
expect(snsMocked.publish).toHaveBeenCalledWith(
{
Message: JSON.stringify({
type: 'DELETE_COMPLETE',
data: {
id: classifiedRequestId,
}
}),
TopicArn: process.env.SNS_ARN
}
);
// expect promise function will be call too
expect(mockPromise).toHaveBeenCalled();
})
});

how to return value from a promise function

I have a function which checks whether a device is online or not. Below is the code.
const ping = require('ping');
export function findDevices(device) {
try {
const hosts = [device];
let result = null;
hosts.forEach((host) => {
ping.promise.probe(host)
.then((res) => {
console.log(res.alive)
result = res.alive;
return {
Status: result
}
});
});
} catch (err) {
logger.error(err, '[ config - findDevices() ]');
console.error(err);
return {
Status: "Failed"
}
}
}
I am calling this function in a redux action like this:
export function start(device) {
return dispatch => {
const status = Connectionstatus.findDevices(device);
return dispatch({
type: actionTypes.CONNECTIONSTATUS,
payload: {
ConnectionStatus: status
}
})
};
}
I am expective the status variable to be either true or false. But i am getting as undefined even though i am returning the value inside then of the promise function. i have tried awaiting this call and still its not working. Any help would be much appreciated. Thanks.
If that's the case you can do like this
const getStatus = async () => {
try {
const hosts = [device];
const promises = [];
hosts.forEach((host) => {
promises.push(ping.promise.probe(host));
});
const result = await Promise.all(promises);
const status = result.map((r) => { return r.alive; });
return status;
} catch (err) {
logger.error(err, '[ config - findDevices() ]');
return { status: 'Failed' };
}
};
Not 100% sure what all the vars are, but have you considered using async/await to simplify things a bit like this?
const getStatus122 = async device => {
return await Promise.all([device].map(ping.promise.probe))
.then(({ alive }) => alive)
.then(Status => ({ Status }))
.catch(error => {
logger.error(error, '[ config - findDevices() ]');
return { Status: 'Failed' };
})
}
More on that here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
With Promises you should inspect the promised result either in when or catch callback functions. With async/await the code may look a bit simpler. Here is the version with explicit promises.
const ping = require('ping');
const Connectionstatus = {
findDevices: (device) => {
return ping.promise.probe(device).then((res) => {
const result = res.alive;
console.log(result);
return {
Status: result,
};
}).catch((err) => {
logger.error(err, '[ config - findDevices() ]');
console.error(err);
return {
Status: "failed"
}
});
}
}
export function start(device) {
return dispatch => {
Connectionstatus.
findDevices(device).
then((status) => {
dispatch({
type: actionTypes.CONNECTIONSTATUS,
payload: {
ConnectionStatus: status
}
})
});
};
}
You may see that error handling moved to the catch callback function while the dispatch is done in the then callback function. And this is the answer to your question.

Testing Chained Promises (Jasmine, React, Karma)

I have run into several situations on my present project where I have a chain of promises that I'm not sure how to deal with.
Here is the relevant code block:
return this.axios.get(path, requestOpts)
.then((response) => {console.log('did authorize: ', response); return response})
.then((response) => {
if (response.data.ok) {
window.localStorage.setItem(path, JSON.stringify(response.data));
console.log("Setting localStorage item ", path, response.data);
return response.data.payloadUrl;
} else {
console.error("Non-ok response for ", path, response.data);
const resp: DisplayTokenResponse = response.data;
//TODO: reject promise?
if (resp.status === "AUTHENTICATION_REQUIRED") {
this.axiosService.goToLoginPage(window.location + '');
}
Promise.reject(response.data.message);
}
});
My test (so far) looks like this:
describe('.authorize()', () => {
let axiosSpy: jasmine.Spy;
beforeEach((done) => {
spyOn(svc, 'keyPath').and.returnValue(path);
spyOn(svc, 'storedToken').and.returnValue(stored);
let response = {
data: {
ok: true,
message: 'test-response',
payloadUrl: 'http://payload-url.com'
}
}
spyOn(svc.axios, 'get').and.callFake(
(path:string, reqOpts:AxiosRequestConfig) => {
return new Promise(() => {
response
});
}, (e) => {
console.log(`failed`);
});
});
describe('should authorize user', () => {
it('when supplied a STRING', () => {
clientId = clientId_string;
});
it('when supplied a NUMBER', () => {
clientId = clientId_number;
});
afterEach((done) => {
svc.authorize(clientId, locationId, screenId).then((result) => {
console.log(`result ${result}`);
done();
}, (e) => {
console.log(`failed with error ${e}`);
done();
});
});
});
});
I can test one-level-down promises, but how to I set up my tests to be able to handle situations like this?
Finally got it figured out. I believe it stemmed from a confusion between creating Promise instances versus their resolvers.
The new beforeEach block looks like this:
beforeEach(() => {
spyOn(svc, 'keyPath').and.returnValue(path);
spyOn(svc, 'storedToken').and.returnValue(stored);
let axiosPromise = new Promise((resolve, reject) => {
var responseData = {
data: {
ok: true,
message: 'test-response',
payloadUrl: 'http://payload-url.com'
}
};
resolve(responseData);
});
spyOn(svc.axios, 'get').and.callFake(
()=>{
return axiosPromise;
}
);
});
My tests now pass.

Categories

Resources