问题
I have created VetCreate and VetEdit functions to create a new record and then edit that record respectively (code below). The problem I have is the successful create response is returned but the newly created id isn't populated in the request to fetch the record.
I have async/await
keywords in place where I think they need to be but the details logged to the console clearly indicate something isn't working as it should. If I try to edit a record after returning to the list screen, the API works as expected and returns the details.
I've added all the code and screenshots I can think of but if anything more is required, please ask.
VetCreate
export const VetCreate = (props) => (
<Create title="Credit Application" aside={<CreateAside />} {...props}>
<TabbedForm toolbar={<VetCreateToolbar />} redirect="show">
<FormTab label="applicant">
<DateInput disabled label="Date Created" source="DateCreated" defaultValue={moment(new Date()).format('YYYY-MM-DD hh:mm:ss')} />
<TextInput disabled label="Agent Name" defaultValue={sessionStorage.getItem('foneBookUser')} source="CreatedBy" />
<TextInput label="First Name" defaultValue={'Adam'} source="FirstName" validate={validateLength} />
<TextInput label="Surname" defaultValue={'Smith'} source="Surname" validate={validateLength} />
<TextInput label="RSA ID Number" defaultValue={4567890987654} source="IDNumber" validate={validateID} />
<SelectInput label="Sex" defaultValue={'M'} source="sex" choices={[
{ id: 'M', name: 'Male' },
{ id: 'F', name: 'Female' },
]} validate={validateSex}/>
<TextInput label="Age of Applicant" defaultValue={45} source="ApplicantAge" validate={validateAge} />
<TextInput label="Business Partner" defaultValue={''} source="BusinessPartner" />
<TextInput label="Sales Person" defaultValue={'Spiderman'} source="SalesPerson" validate={validateLength} />
<TextInput label="Cell Phone Number" defaultValue={'345678'} source="CellNumber" validate={validateLength} />
<TextInput label="Email Address" defaultValue={'adam@email.com'} source="Email" validate={validateEmail} />
</FormTab>
<FormTab label="bureau">
<TextInput label="Bureau Score" defaultValue={123} source="BureauScore" validate={validateBureauScore} />
<TextInput label="Gross Monthly Income" defaultValue={30000} source="GrossMonthlyIncome" validate={validateGross} />
<TextInput label="Total Monthly Instalment" defaultValue={3000} source="TotalMonthlyInstalment" validate={validateInstal} />
<TextInput label="Home Postcode" defaultValue={'1122'} source="HomePostCode" validate={validateCode} />
<TextInput label="Number of Properties" defaultValue={11} source="NumProperties" validate={validateNumber} />
<TextInput label="Number of Companies" defaultValue={31} source="NumCompanies" validate={validateNumber} />
</FormTab>
</TabbedForm>
</Create>
);
VetEdit
export const VetEdit = props => {
const classes = useStyles();
return (
<Edit aside={<EditAside />} {...props}>
<SimpleForm toolbar={<VetEditToolbar />} warnWhenUnsavedChanges>
<TextInput disabled label="First Name" source="FirstName" formClassName={classes.inlineBlock} />
<TextInput disabled label="Surname" source="Surname" formClassName={classes.inlineBlock} />
<TextInput disabled label="RSA ID Number" source="IDNumber" formClassName={classes.inlineBlock} />
<TextInput disabled source="Sex" formClassName={classes.inlineBlock} />
<NumberInput disabled source="ApplicantAge" formClassName={classes.inlineBlock} />
<TextInput disabled source="CellNumber" formClassName={classes.inlineBlock} />
<TextInput disabled source="Email" formClassName={classes.inlineBlock} />
<TextInput disabled source="BusinessPartner" formClassName={classes.inlineBlock} />
<TextInput disabled source="SalesPerson" formClassName={classes.inlineBlock} />
<TextInput disabled source="HomePostCode" formClassName={classes.inlineBlock} />
<NumberInput disabled source="NumProperties" formClassName={classes.inlineBlock} />
<NumberInput disabled source="NumCompanies" formClassName={classes.inlineBlock} />
<NumberInput disabled source="GrossMonthlyIncome" formClassName={classes.inlineBlock} />
<NumberInput disabled source="TotalMonthlyInstalment" formClassName={classes.inlineBlock} />
<NumberInput disabled source="BureauScore" formClassName={classes.inlineBlock} />
<SelectInput label="ID Verified" source="IDVerified" choices={[
{ id: 'N', name: 'No' },
{ id: 'Y', name: 'Yes'}
]} formClassName={classes.inlineBlock} />
<SelectInput label="Debt Review" source="DebtReview" choices={[
{ id: 'N', name: 'No' },
{ id: 'Y', name: 'Yes'}
]} formClassName={classes.inlineBlock} />
<SelectInput label="Poor Payment Profile" source="PoorPaymentProfile" choices={[
{ id: 'N', name: 'No' },
{ id: 'Y', name: 'Yes'}
]} formClassName={classes.inlineBlock} />
<SelectInput label="Adverse" source="Adverse" choices={[
{ id: 'N', name: 'No' },
{ id: 'Y', name: 'Yes'}
]} formClassName={classes.inlineBlock} />
<SelectInput label="Fraud Suspected" source="SuspectFraud" choices={[
{ id: 'N', name: 'No' },
{ id: 'Y', name: 'Yes'}
]} formClassName={classes.inlineBlock} />
<TextInput label="ID Validated" source="IDValidated" formClassName={classes.inlineBlock} />
<TextInput label="Bank Statement Validated" source="BankStatementValidated" formClassName={classes.inlineBlock} />
<TextInput label="Payslip Validated" source="PayslipValidated" formClassName={classes.inlineBlock} />
<TextInput label="Proof of Residence Validated" source="ResProofValidated" formClassName={classes.inlineBlock} />
</SimpleForm>
</Edit>
)
};
DataProvider.js
import { fetchUtils } from 'react-admin';
import { stringify } from 'query-string';
const apiUrl = 'http://localhost:8081';
const httpClient = (url, options = {}) => {
if (!options.headers) {
options.headers = new Headers({
'Accept': 'application/json',
'Content-Type': 'application/json'
});
}
// add your own headers here
options.headers.set(
'X-Custom-Header',
'Access-Control-Allow-Headers',
'Access-Control-Allow-Origin',
'*',
);
//console.log('4 options: ', options);
return fetchUtils.fetchJson(url, options);
};
export default {
getList: (resource, params) => {
//console.log('params: ', params);
const { page, perPage } = params.pagination;
const { field, order } = params.sort;
const query = {
sort: JSON.stringify([field, order]),
range: JSON.stringify([(page - 1) * perPage, page * perPage - 1]),
filter: JSON.stringify(params.filter),
};
const url = `${apiUrl}/${resource}?${stringify(query)}`;
//console.log('url: ', url);
return httpClient(url).then(({ headers, json }) => ({
data: json,
total: parseInt(headers.get('content-range').split('/').pop(), 10),
}));
},
getOne: (resource, params) =>
httpClient(`${apiUrl}/${resource}/${params.id}`).then(({ json }) => ({
data: json,
})),
getMany: (resource, params) => {
const query = {
filter: JSON.stringify({ id: params.ids }),
};
const url = `${apiUrl}/${resource}?${stringify(query)}`;
return httpClient(url).then(({ json }) => ({ data: json }));
},
getManyReference: (resource, params) => {
const { page, perPage } = params.pagination;
const { field, order } = params.sort;
const query = {
sort: JSON.stringify([field, order]),
range: JSON.stringify([(page - 1) * perPage, page * perPage - 1]),
filter: JSON.stringify({
...params.filter,
[params.target]: params.id,
}),
};
const url = `${apiUrl}/${resource}?${stringify(query)}`;
return httpClient(url).then(({ headers, json }) => ({
data: json,
total: parseInt(headers.get('content-range').split('/').pop(), 10),
}));
},
update: (resource, params) =>
httpClient(`${apiUrl}/${resource}/${params.id}`, {
method: 'PUT',
body: JSON.stringify(params.data),
}).then(({ json }) => ({ data: json })),
updateMany: (resource, params) => {
const query = {
filter: JSON.stringify({ id: params.ids}),
};
return httpClient(`${apiUrl}/${resource}?${stringify(query)}`, {
method: 'PUT',
body: JSON.stringify(params.data),
}).then(({ json }) => ({ data: json }));
},
create: (resource, params) =>
httpClient(`${apiUrl}/${resource}`, {
method: 'POST',
body: JSON.stringify(params.data),
}).then(({ json }) => ({
data: { ...params.data, id: json.id },
})),
delete: (resource, params) =>
httpClient(`${apiUrl}/${resource}/${params.id}`, {
method: 'DELETE',
}).then(({ json }) => ({ data: json })),
deleteMany: (resource, params) => {
const query = {
filter: JSON.stringify({ id: params.ids}),
};
return httpClient(`${apiUrl}/${resource}?${stringify(query)}`, {
method: 'DELETE',
body: JSON.stringify(params.data),
}).then(({ json }) => ({ data: json }));
}
};
Vet API
Vet.createVet = async function(newVet, result) {
console.log('newVet: ', newVet);
await sql.query("INSERT INTO vets set ?", newVet, function(err, res) {
if(err) {
console.log('err: ', err);
result(err, null);
} else {
console.log('res.insertId: ', res.insertId);
result(null, res.insertId)
}
});
};
Vet.getVetById = async function (VetId, result) {
console.log('getVetById: ', VetId);
await sql.query("Select * from vets WHERE id = ?", VetId, function (err, res) {
if(err) {
console.log("error: ", err);
result(null, err);
} else {
console.log('vets : ', res);
result(null, res);
}
});
};
回答1:
await
only does something useful when you await
a promise. Statements like this:
await sql.query("INSERT INTO vets set ?", newVet, function(err, res) {...});
are NOT awaiting a promise. That query does not return a promise. The result of that operation comes only in that callback, not in any returned promise so await
has nothing to do. There is no magic in await
that somehow knows when the plain callback is done. No, await
operates on a promise and if you don't give it a promise, it has nothing to do (doesn't wait for anything).
Instead, you need to use database operations that DON'T use callbacks, but instead return promises if you want to use await
with them.
If you're using mysql, see mysql2 for promise support and then rewrite all your database statements to use the returned promise to get the results from and do not pass the database call a plain callback.
回答2:
In case anyone else runs into this problem and wasn't aware of the promise - async/await relationship, I found this article to be very helpful.
来源:https://stackoverflow.com/questions/62256175/how-do-i-slow-down-my-express-server-response-to-allow-the-react-admin-getone