I'm new to express.
for routing I create an object in a file and export the object
import userController from "../controllers/userController.js"
// get all users
router.get("/", isAuth, isAdmin, userController.list)
the problem is, this is undefined on userController
const userController = {
_statusCode: 200,
async list( req, res ) {
console.log(this);
try {
const users = await User.find()
return res.status( this._statusCode ).json( respSC( users, this._statusCode ) )
} catch( err ) {
this.statusCode = 500
return res.status( this._statusCode ).json( respER( this._statusCode, err ) )
}
}
}
export default userController
OR
const userController = {
_statusCode: 200,
list: async function( req, res ) {
console.log(this);
try {
const users = await User.find()
return res.status( this._statusCode ).json( respSC( users, this._statusCode ) )
} catch( err ) {
this.statusCode = 500
return res.status( this._statusCode ).json( respER( this._statusCode, err ) )
}
}
}
export default userController
OR
const userController = {
_statusCode: 200,
list: async ( req, res ) => {
console.log(this);
try {
const users = await User.find()
return res.status( this._statusCode ).json( respSC( users, this._statusCode ) )
} catch( err ) {
this.statusCode = 500
return res.status( this._statusCode ).json( respER( this._statusCode, err ) )
}
}
}
export default userController
in all three ways, the log is :
undefined
file:///C:/NodeJS/<project-folder>/app/controllers/userController.js:18
return res.status( this._statusCode ).json( respER( this._statusCode, err ) )
^
TypeError: Cannot read properties of undefined (reading '_statusCode')
at list (file:///C:/NodeJS/<project-folder>/app/controllers/userController.js:18:31)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
I know about objects and I try create object in chrome console and this is defined, but in routing of express, this is undefined.
can anybody explain why it's happen?
First, when you pass userController.list in this:
router.get("/", isAuth, isAdmin, userController.list)
The JS interpreter reaches into the userController object and gets a reference to the list method and what it passes is just a reference to that method all by itself and will have no connection at all to the userController object at all. Thus, the list() method will just be called as a regular function by Express (because it doesn't even know about the userController object and (if operating in strict mode), then calling a regular function leaves the this value as undefined.
So, that explains why what you observe is happening.
Now, how to fix that. The simplest way would be use .bind():
router.get("/", isAuth, isAdmin, userController.list.bind(userController));
This passes a dynamically generated stub function to Express and when Express calls that stub function and the stub function then calls your list method in the right way to set the this value inside it to userController.
There are other ways to do this too:
You could change the way the methods are declared so they will automatically bind to their parent object.
You could write your own sub function that calls list as userController.list() and you pass your own stub function to Express.
You can use utility functions in your class that auto-bind methods.
There's even a new ECMAScript syntax proposal for a binding operator with ::.
Here's a simple code example that shows both the undefined you're seeing and the solution. The first example fn1 is what your current code is doing. The second example fn2 is my suggested solution.
"use strict";
class MyObj {
constructor() {
this.greeting = "Hi";
}
getGreeting() { console.log(this); }
};
const obj = new MyObj();
// get a non-bound method and call it
const fn1 = obj.getGreeting;
fn1();
// get a bound method and call it
const fn2 = obj.getGreeting.bind(obj);
fn2();
Related
I'd like to know if it's possible to make 2 API calls inside a loader function if I am using react-router 6. My ideas was to create an object based on these 2 calls and destruct the object in the rendering component like this:
function MainComponent (){
const {data , reservation} = useRouteLoaderData('room-details');
..
..
}
export default MainComponent;
export async function loader({request, params}) {
const id = params.roomId;
const response = await fetch ('http://localhost:8080/rooms/' + id);
const response2 = await fetch('http://localhost:8080/rooms/reservation/' + id)
const megaResponse = {
data: response, //i tried data:{respose} It ain't work
reservation: response2,
};
if (!response.ok) {
throw json({message: 'Something Wrong'}, {status: 500});
}
else {
return megaResponse;
}
}
But i have no success output.
I'd really want to make these 2 call in one place, otherwise I will have to use useEffect in a child component. Not a good Idea I think.
Thanks
I suspect you are not returning the unpacked response, i.e. JSON. I suggest surrounding the asynchronous code in a try/catch and simply try to process the requests/responses. Unpack the JSON value from the response objects. Since it doesn't appear the requests are dependent on one another I recommend loading them into an array of Promises that can be run concurrently and awaited as a whole. If during any part of the processing a Promise is rejected or an exception thrown, the catch block will return the JSON error response to the UI, otherwise, the { data, reservation } object is returned.
const loader = async ({ request, params }) => {
const { roomId } = params;
try {
const [data, reservation] = await Promise.all([
fetch("http://localhost:8080/rooms/" + roomId),
fetch("http://localhost:8080/rooms/reservaton/" + roomId)
]).then((responses) => responses.map((response) => response.json()));
return { data, reservation };
} catch {
throw json({ message: "Something Wrong" }, { status: 500 });
}
};
I found the solution, I tried it and it worked. It is as follow:
function MainComponent (){
const [data , reservation] = useRouteLoaderData('room-details');
..
..
}
export default MainComponent;
export async function loader({request, params}) {
const id = params.roomId;
return Promise.all([
fetch ('http://localhost:8080/rooms/' + id),
fetch('http://localhost:8080/rooms/reservation/' + id)
])
.then(
([data, reservation]) =>
Promise.all([data.json(), reservation.json()]),
error => {throw json({message: 'Something Wrong'}, {status: 500});}
)
.then(([data, reservation]) => {
return [data, reservation];
});
}
Thanks
I am writing a hook to make a post request that returns two properties, sessionId and sessionData. I am using this hook in a component. My hook looks like this.
export const createOrder = async () => {
try {
const response = await postWithToken(API_ROUTES.PAYMENT_SESSION, token || '',
testObject)
console.log("FROM HOOK", response)
return response
} catch (err: any) {
console.log(err)
}
}
And my component look like this
const myComponent = () => {
useEffect(() => {
createOrder().then(data => {
console.log("Session Data",data.sessionData)
console.log("FROM PAGE", data)
})
}, [])
return (
<div />
)
}
When I try to access data.sessionData on the component I get the error that sessionDta does not exist on type void. But If I check the logs on the console I get the same object on the component and on the hook. Also If I check on my component the typeof data I get an object.
Why I am getting this error?
You don't return anything from your catch block, so the return type of your function is Promise<WhateverTheTypeOfResponseIs | void> (N.B. async functions implicitly return a Promise, and if your postWithToken function doesn't return anything then it's just Promise<void>), depending on which code path happens.
In the future you can avoid unpleasant and slightly problematic to debug issues like this by giving your functions an explicit return type and in this case the compiler will let you know that your expectation was violated:
const postWithToken = async (route: string, token: string, obj: any): Promise<boolean> => {
try {
const resp = await fetch(
route,
{
method: 'POST',
body: JSON.stringify(Object.assign(obj, { token }))
},
)
return Boolean(resp.status < 400 && resp.status >= 200)
} catch (err) {
console.error(err)
return false
}
}
const API_ROUTES = {
PAYMENT_SESSION: 'whatever'
}
const testObject = {
token: ''
}
const token = ''
const createOrder = async (): Promise<boolean> => { // <-- squiggles
try {
const response = await postWithToken(API_ROUTES.PAYMENT_SESSION, token || '',
testObject)
console.log("FROM HOOK", response)
return response
} catch (err: any) {
console.log(err)
}
}
Playground
The types in the example I created may be different (you'll need to sub with the actual types from your code) but you should get the idea. You can fix this by any of the following:
Explicitly return something of the correct type from the catch block.
Change your return type to Promise<CorrectType | undefined>.
Move the error handling to the caller as suggested by goto1 in the comments.
Also note that as goto1 points out in the comments on the question, your hook isn't actually a hook (which is fine, but be careful of terminology).
This sounds like a TypeScript issue, so I suggest to provide the proper return type for your createOrder function.
// Use the official type for SessionData, if you have it available,
// otherwise create a new type that properly matches its shape
type CreateOrderResult = {
sessionData: SessionData;
}
// No need for `async/await` here, just return the `postWithToken`
// and handle errors inside your component
export const createOrder = (): Promise<CreateOrderResult> => {
// ...
return postWithToken(
API_ROUTES.PAYMENT_SESSION,
token || '',
testObject
)
}
// MyComponent.tsx
const MyComponent = () => {
useEffect(() => {
createOrder()
.then(data => console.log(data.sessionData))
.catch(error => /* handle errors appropriately */)
}, [])
}
open tsconfig.json file and
change
"strict": true,
into
"strict": false,
this usually works for me
check out https://v2.vuejs.org/v2/guide/typescript.html for more information
I am using Firebase cloud functions (node.js) for my app and I want to create a shared wrapper for all of my endpoints that will handle errors and have some checks.
Is it possible to create a wrapper function for the cloud functions and then expose it as a new endpoint?
Example:
// normal function that works
const helloWorld = functions.https.onRequest((req, res) => {
functions.logger.log("hello world!");
return res.end();
})
// the wrapper function that handles errors and shared logic
const functionsWrapper = async ({allowedMethods, handler}) =>
functions.https.onRequest(async (req, res) => {
try {
if (allowedMethods) {
if (!allowedMethods.includes(req.method.toLowerCase())) {
return res.status(405).end();
}
}
const result = await handler;
return res.json(result);
} catch (error) {
//handling errors here
}
}
);
// the function I want to wrap and then expose as an endpoint
const anotherFunc = functionsWrapper({
allowedMethods: ['get'],
async handler() {
return {message: 'I am inside functionsWrapper'}
}
})
// exposing the functions
module.exports = {
helloWorld, // helloWorld can be called and gets an endpoint
anotherFunc // anotherFunc does not get an endpoint
}
I think that Firebase finds where functions.https.onRequest is being exported and then exposes it as an endpoint but how can I expose 'anotherFunc'?
Or in short how to expose:
const endpoint = () => functions.htpps.onRequest((req, res) => {})
Thanks!
utils file
const isStatusError = (err: any): err is StatusError =>
err.status !== undefined;
export const handleError = async (err: any, emailer?: Mailer) => {
const sendErrorEmail = async (
subject: string,
text: string,
emailer?: Mailer
) => {
try {
const mail: Pick<Mail, "from" | "to"> = {
from: config.email.user,
to: config.email.user,
};
// 2. This throws an error
await emailer?.send({ ...mail, subject, text });
} catch (err) {
// 3. It should call this function recursively...
await handleError(new EmailError(err), emailer);
}
};
if (isStatusError(err)) {
if (err instanceof ScrapeError) {
console.log("Failed to scrape the website: \n", err.message);
}
if (err instanceof AgendaJobError) {
console.log("Job ", err.message);
// #TODO
}
if (err instanceof RepositoryError) {
console.log("Repository: ");
console.log(err.message);
// #TODO
}
// 4. and eventually come here and end the test...
if (err instanceof EmailError) {
console.log("Failed to create email service", err);
}
// 1. It goes here first.
if (err instanceof StatusError) {
console.log("generic error", err);
await sendErrorEmail("Error", "", emailer);
}
} else {
if (err instanceof Error) {
console.log("Generic error", err.message);
}
console.log("Generic error", err);
}
};
test file
import * as utils from "./app.utils";
import { Mailer } from "./services/email/Emailer.types";
import { StatusError } from "./shared/errors";
const getMockEmailer = (implementation?: Partial<Mailer>) =>
jest.fn<Mailer, []>(() => ({
service: "gmail",
port: 5432,
secure: false,
auth: {
user: "user",
pass: "pass",
},
verify: async () => true,
send: async () => true,
...implementation,
}))();
describe("error handling", () => {
it("should handle email failed to send", async () => {
const mockEmailer = getMockEmailer({
send: async () => {
throw new Error();
},
});
// This line is the problem. If I comment it out, it's all good.
const spiedHandleError = jest.spyOn(utils, "handleError");
// #TODO: Typescript will complain mockEmailer is missing a private JS Class variable (e.g. #transporter) if you remove `as any`.
await utils.handleError(new StatusError(500, ""), mockEmailer as any);
expect(spiedHandleError).toBeCalledTimes(2);
});
});
This test runs forever, and it is because I made handleError a spy function.
I tried to import itself and run await utils.handleError(new EmailError(err), emailer) but it still continue to hang.
So what happens is:
It throws an Error.
It will then figure out it is a StatusError which is a custom error, and it will output the error and call a function to send an email.
However, attempting to send an email throws another Error
It should then call itself with EmailError
It will detect it is an EmailError and only output the error.
Logic wise, there is no infinite loop.
In the utils file, if you comment this const spiedHandleError = jest.spyOn(utils, "handleError"); out, the test will be fine.
Is there a way around this somehow?
I realized it's my own logic that caused the infinite loop. I forgot to add the return statement to each of my if statement.
My spy function now works.
const spiedHandleError = jest.spyOn(utils, "handleError");
await utils.handleError({
err: new StatusError(500, "error"),
emailer: mockEmailer,
});
expect(spiedHandleError).toBeCalledTimes(2);
expect(spiedHandleError.mock.calls).toEqual([
[{ err: new StatusError(500, "error"), emailer: mockEmailer }],
[
{
err: new EmailError("failed to send an error report email."),
emailer: mockEmailer,
},
],
]);
It's impossible to spy or mock a function that is used in the same module it was defined. This is the limitation of JavaScript, a variable cannot be reached from another scope. This is what happens:
let moduleObj = (() => {
let foo = () => 'foo';
let bar = () => foo();
return { foo, bar };
})();
moduleObj.foo = () => 'fake foo';
moduleObj.foo() // 'fake foo'
moduleObj.bar() // 'foo'
The only way a function can be written to allow this defining and consistently using it as a method on some object like CommonJS exports:
exports.handleError = async (...) => {
...
exports.handleError(...);
...
};
This workaround is impractical and incompatible with ES modules. Unless you do that, it's impossible to spy on recursively called function like handleError. There's babel-plugin-rewire hack that allows to do this but it's known to be incompatible with Jest.
A proper testing strategy is to not assert that the function called itself (such assertions may be useful for debugging but nothing more) but assert effects that the recursion causes. In this case this includes console.log calls.
There are no reasons for spyOn to cause infinite loop. With no mock implementation provided, it's just a wrapper around original function. And as explained above, there's no way how it can affect internal handleError calls, so it shouldn't affect the way tested function works.
It's unsafe to spy on utils ES module object because it's read-only by specification and can result in error depending on Jest setup.
I am developing nodejs app with mongoDB. I have written a function which will filter some data from mongodb and store in variable "docs". I tried several methods to declare a function globally and tried to access that variable "docs" but I couldn't.`
conn.then(client=> client.db('myprojectone').collection('offlinemessage').find({nameTo:"sd"}).limit(1).toArray(function(err, docs) {
if(err) { console.error(err) }
res.send(JSON.stringify(docs))
console.log(docs);
return docs;
}))
`
Make one helper function which performs all your requirement along with DB query and filter result and returns that result. Exports this function using module.exports like below.
utils.js // Helper file
const utils = {};
utils.getData = async () => {
try {
// ...Your business logic
return true; // It will be your variable
} catch (err) {
logger.error(err);
throw err;
}
};
module.exports = utils;
call utils.getData() to get your result;
userController
const utils = require('./helper/utils');
const userController = {};
userController.getData = async () => {
try {
const result = await utils.getData();
} catch (err) {
logger.error(err);
throw err;
}
};
module.exports = userController;
You can't access async function variable outside of function.
Express.js Gives simple thing to access global functions
fnName = function(){ return "hi"; }; // i.e. don't do: var name = function(){ ... };
console.log(fnName()); // this prints "hi"
console.log(global.fnName()); // this also prints "hi" - it was assigned to global.
One possible solution would be to create a service js class file, called utils.js and then add in this class that you require globally,
utils.js
export default class Example {
static getDocs() {
// Rest of the global method
}
}
And in the place that you require the docs variable, you simply call the method getDocs()
import Utils from '<location_to_utils.js>';
console.log(Utils.getDocs());