can't call .catch on a function unless it's wrapped? - asynchronous

I have an async function such as this
router.get("/", async function (req, res, next) {
const posts = await Post.find({});
res.render("index", { title: "Homepage", posts });
});
and I can wrap it in a function that handles its catch
function catchAsync(fn) {
return function (req, res, next) {
fn(req, res, next).catch((e) => next(e));
};
}
so it looks like this
router.get(
"/",
catchAsync(async function (req, res, next) {
const posts = await Post.find({});
res.render("index", { title: "Homepage", posts });
})
);
but my doubt, and the reason for this post, is why can't I call .catch directly on the async function, like so?
router.get(
"/",
async function (req, res, next) {
const posts = await Post.find({});
res.render("index", { title: "Homepage", posts });
}.catch((e) => next(e))
);
I understand I can do this however
router.get("/", async function (req, res, next) {
my_callback(req, res, next).catch((e) => {
next(e);
});
});
let my_callback = async function (req, res, next) {
const posts = await Post.find({});
res.render("index", { title: "Homepage", posts });
};

Here you are calling .catch on a Promise returned by calling my_callback function
router.get("/", async function (req, res, next) {
my_callback(req, res, next).catch((e) => {
next(e);
});
});
Here you are trying to access .catch on a function definition which is syntactically wrong in JavaScript
router.get(
"/",
async function (req, res, next) {
const posts = await Post.find({});
res.render("index", { title: "Homepage", posts });
}.catch((e) => next(e))
);
If you want to include catch with the function definition then you can use try...catch
More reading about promises - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

Related

How do I set a loading state when i'm fetching data outside a page function in next 13?

From the docs, this is how you fetch data, but since it's outside the function, how do I track the loading state so users can know when a data section is loading.
async function getData() {
const res = await fetch('https://api.example.com/...');
if (!res.ok) {
throw new Error('Failed to fetch data');
}
return res.json();
}
export default async function Page() {
const data = await getData();
return <main></main>;
}
You can use custom hooks for that:
useData.js
async function useData() {
const [loading, setLoading] = useState(true)
cost [error, setError] = useState(false)
const [data, setData] = useState()
useEffect(() => {
const fetchData = async () => {
setLoading(true)
const res = await fetch('https://api.example.com/...');
if (!res.ok) {
setError(true)
return
}
setError(false)
setLoading(false)
setData(res.json())
}
fetchData()
}, [])
return {loading, error, data}
}
//page
export default function Page() {
const {data, loading, error} = useData();
if(loading){ return <p>Loading...</p>}
if(error){ return <p>Error...</p>}
if(!data){ return <p>No data!!!</p>}
return <main></main>;
}
Later, you can set up your hook to use an uri param so you can reuse this hook in multiple pages.

Promise not working, but Async/Await method working

I'm trying to figure out why I'm not able to retrieve data from the postgres database.
It works when I use async await, but when I try to to use Promise with .then(result).catch(error), it's not working.
Console log gives me Promise { <pending> }
getUsers
your text`const db = require("../config/db");
const getUsers = () => {
const query = "SELECT * FROM users";
const users = db
.query(query)
.then((result) => {
return result.rows;
})
.catch((error) => {
return error.message;
});
return users;
};
module.exports = {
getUsers,
};
index.js (Using Promise) -- Doesn't work.
const { getUsers } = require('../helpers/users')
export default function Home(props) {
return (
<ul>
{props.name}
{props.users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
export function getServerSideProps(context) {
const users = getUsers()
.then((result) => {
return result.rows;
})
.catch((error) => {
return error.message;
})
return {
props: {
users
}
}
}
index.js (Using Async/Await) -- Works.
export async function getServerSideProps(context) {
const users = await getUsers()
return {
props: {
users
}
}
}
That should work (according the Next doc getServerSideProps is async function). So just add async keyword and return promise from that function:
export async function getServerSideProps(context) {
return getUsers()
.then((result) => {
return {props:{user:result.rows}};
})
.catch((error) => {
return {props:{user:error.message}};
})
}
Thank you Andrey and Bergi.
Since it's Promise, I did not have to include the async, but the rest of the code did indeed work!
export function getServerSideProps(context) {
getUsers()
.then((result) => {
return {
props: {
users: result
}
}
})
.catch((error) => {
return {
props: {
error: error.message
}
}
})
}

Can i dispatch many actions in getServersideprops?

In my social media app in Home page i want to dispatch 3 actions from my api:
posts , users , userDetails
But this may show an error(500) on vercel because the request takes a lot of time to get all these data.
vercel Error
this error will not appear again after refreshing the page !!!
i think that's because the request takes a lot of time to get all the data.
-> getServersideProps Code
export const getServerSideProps = wrapper.getServerSideProps(
store => async (context) =>
{
const {req} = context
const session = await getSession({ req });
await store.dispatch(fetchPostsAction());
await store.dispatch(fetchUsersAction(4));
await store.dispatch(LoggedInUserAction({email:session.user.email}));
})
-> fetchPostsAction Code
"post/list",
async (_, { rejectWithValue, getState, dispatch }) => {
try
{
let link = `${URL}/api/posts`;
const { data } = await axios.get(link,{
headers: { "Accept-Encoding": "gzip,deflate,compress" }
});
console.log("#2 got the data",data)
return data;
} catch (error) {
if (!error?.response) throw error;
return rejectWithValue(error?.response?.data);
}
}
);
-> extraReducer Code
builder.addCase(createpostAction.pending, (state, action) => {
state.createPostLoading = true;
});
builder.addCase(createpostAction.fulfilled, (state, action) => {
state.postLists = [...state.postLists, action.payload.post].sort((a, b) => b.createdAt > a.createdAt ? 1 : -1)
state.createPostLoading = false;
state.isCreated = true;
state.appErr = null;
state.serverErr = null;
});
builder.addCase(createpostAction.rejected, (state, action) => {
state.createPostLoading = false;
state.appErr =
action?.payload?.message || action?.payload?.error?.message;
state.serverErr = action?.error?.message;
});
-> get posts from api Code
handler.get(async (req, res) =>
{
await db.connect();
try {
const posts = await Post.find().populate({
path: 'user',
model: 'User',
}).populate({
path:'comments',
options: {sort: {'createdAt' : -1} }
}).sort('-createdAt')
res.status(200).json({
success:true,
posts
});
} catch (err) {
res.status(500).json(err.message)
}
await db.disconnect();
})
so what is the best way to fetch all these data in next js ?
I hope there is a way to solve this problem

next-redux-wrapper Wrapper.getStaticProps not working with

This is my backend controller, I am getting the rooms data
const allRooms = catchAsyncErrors(async (req, res) => {
const resPerPage = 4;
const roomsCount = await Room.countDocuments();
const apiFeatures = new APIFeatures(Room.find(), req.query)
.search()
.filter()
let rooms = await apiFeatures.query;
let filteredRoomsCount = rooms.length;
apiFeatures.pagination(resPerPage)
rooms = await apiFeatures.query;
res.status(200).json({
success: true,
roomsCount,
resPerPage,
filteredRoomsCount,
rooms
})
})
This is my redux actions I am sending the payload and data
export const getRooms = (req, currentPage = 1, location = '', guests, category) => async (dispatch) => {
try {
const { origin } = absoluteUrl(req);
let link = `${origin}/api/rooms?page=${currentPage}&location=${location}`
if (guests) link = link.concat(`&guestCapacity=${guests}`)
if (category) link = link.concat(`&category=${category}`)
const { data } = await axios.get(link)
dispatch({
type: ALL_ROOMS_SUCCESS,
payload: data
})
} catch (error) {
dispatch({
type: ALL_ROOMS_FAIL,
payload: error.response.data.message
})
}
}
This is my reducer function, dispatching the room data
export const allRoomsReducer = (state = { rooms: [] }, action) => {
switch (action.type) {
case ALL_ROOMS_SUCCESS:
return {
rooms: action.payload.rooms
}
case ALL_ROOMS_FAIL:
return {
error: action.payload
}
case CLEAR_ERRORS:
return {
...state,
error: null
}
default:
return state
}
}
Here I want to get the data using wrapper.getStaticProps but I am getting an error, but when i am using wrapper.getServerSideProps I get the data.
export const getStaticProps = wrapper.getStaticProps(store=> async ({ req, query, }) => {
await store.dispatch(getRooms(req, query.page, query.location, query.guests, query.category))
})
It seems that in ({ req, query, }) => , query is undefined.
Going by the documentation of next, the context object for getStaticProps has no property query. It is only available in getServerSideProps.
See also this info:
Only runs at build time
Because getStaticProps runs at build time, it does not receive data that’s only available during request time, such as query parameters or HTTP headers as it generates static HTML.

Handling multipart/form-data POST with Express in Cloud Functions

I've been trying to handle POSTs (multipart/form-data) with a Firebase function and Express but it just doesn't work. Tried this in local server and it works just fine. Everything's the same except it's not contained in a Firebase function.
Besides screwing up the request object it seems it also screws up the way busboy works.
I've tried different solutions presented here but they just don't work. As one user mentions, the callbacks passed to busboy (to be called when a 'field' is found or when it finishes going through the data) are never called and the function just hangs.
Any ideas?
Here's my function's code for reference:
const functions = require('firebase-functions');
const express = require('express');
const getRawBody = require('raw-body');
const contentType = require('content-type')
const Busboy = require('busboy');
const app = express();
const logging = (req, res, next) => {
console.log(`> request body: ${req.body}`);
next();
}
const toRawBody = (req, res, next) => {
const options = {
length: req.headers['content-length'],
limit: '1mb',
encoding: contentType.parse(req).parameters.charset
};
getRawBody(req, options)
.then(rawBody => {
req.rawBody = rawBody
next();
})
.catch(error => {
return next(error);
});
};
const handlePostWithBusboy = (req, res) => {
const busboy = new Busboy({ headers: req.headers });
const formData = {};
busboy.on('field', (fieldname, value) => {
formData[fieldname] = value;
});
busboy.on('finish', () => {
console.log(`> form data: ${JSON.stringify(formData)}`);
res.status(200).send(formData);
});
busboy.end(req.rawBody);
}
app.post('/', logging, toRawBody, handlePostWithBusboy);
const exchange = functions.https.onRequest((req, res) => {
if (!req.path) {
req.url = `/${req.url}`
}
return app(req, res)
})
module.exports = {
exchange
}
The documentation Doug referred to in his answer is good. An important caveat though is that rawBody does not work in the emulator. The workaround is to do:
if (req.rawBody) {
busboy.end(req.rawBody);
}
else {
req.pipe(busboy);
}
As described in this issue:
https://github.com/GoogleCloudPlatform/cloud-functions-emulator/issues/161#issuecomment-376563784
Please read the documentation for handling multipart form uploads.
... if you want your Cloud Function to process multipart/form-data, you can use the rawBody property of the request.
Because of the way Cloud Functions pre-processes some requests, you can expect that some Express middleware will not work, and that's what you're running into.
I've combined the previous two answers into a easy-to-use async function.
const Busboy = require('busboy');
const os = require('os');
const path = require('path');
const fs = require('fs');
module.exports = function extractMultipartFormData(req) {
return new Promise((resolve, reject) => {
if (req.method != 'POST') {
return reject(405);
} else {
const busboy = new Busboy({ headers: req.headers });
const tmpdir = os.tmpdir();
const fields = {};
const fileWrites = [];
const uploads = {};
busboy.on('field', (fieldname, val) => (fields[fieldname] = val));
busboy.on('file', (fieldname, file, filename) => {
const filepath = path.join(tmpdir, filename);
const writeStream = fs.createWriteStream(filepath);
uploads[fieldname] = filepath;
file.pipe(writeStream);
const promise = new Promise((resolve, reject) => {
file.on('end', () => {
writeStream.end();
});
writeStream.on('finish', resolve);
writeStream.on('error', reject);
});
fileWrites.push(promise);
});
busboy.on('finish', async () => {
const result = { fields, uploads: {} };
await Promise.all(fileWrites);
for (const file in uploads) {
const filename = uploads[file];
result.uploads[file] = fs.readFileSync(filename);
fs.unlinkSync(filename);
}
resolve(result);
});
busboy.on('error', reject);
if (req.rawBody) {
busboy.end(req.rawBody);
} else {
req.pipe(busboy);
}
}
});
};

Resources